
雪花生成过程https://baike.baidu.com/item/%E9%9B%AA%E8%8A%B1/8012054?fraladdin在冰晶增长的同时冰晶附近的水汽会被消耗。所以越靠近冰晶的地方水汽越稀薄过饱和程度越低。在紧靠冰晶表面的地方因为多余的水汽都已凝华在冰晶上了所以刚刚达到饱和。这样靠近冰晶处的水汽密度就要比离它远的地方小。水汽就从冰晶周围向冰晶所在处移动。水汽分子首先遇到冰晶的各个角棱和凸出部分并在这里凝华而使冰晶增长。于是冰晶的各个角棱和凸出部分将首先迅速地增长而逐渐成为枝叉状。以后又因为同样的原因在各个枝叉和角棱处长出新的小枝叉来。与此同时在各个角棱和枝叉之间的凹陷处。空气已经不再是饱和的了。有时在这里甚至有升华过程以致水汽被输送到其他地方去。这样就使得角棱和枝叉更为突出而慢慢地形成了我们熟悉的星状雪花。分布式中ID的常用解决方案https://blog.csdn.net/m0_37041378/article/details/78125747在复杂的系统中,往往需要对大量的数据如订单,账户进行标识,以一个有意义的有序的序列号来作为全局唯一的ID;而分布式系统中我们对ID生成器要求又有哪些呢?全局唯一性不能出现重复的ID号既然是唯一标识这是最基本的要求。递增比较低要求的条件为趋势递增,即保证下一个ID一定大于上一个ID,而比较苛刻的要求是连续递增,如1,2,3等等。高可用高性能ID生成事关重大,一旦挂掉系统崩溃;高性能是指必须要在压测下表现良好,如果达不到要求则在高并发环境下依然会导致系统瘫痪。信息安全如果ID是连续的恶意用户的扒取工作就非常容易做了直接按照顺序下载指定URL即可如果是订单号就更危险了竞对可以直接知道我们一天的单量。所以在一些应用场景下会需要ID无规则、不规则。第二条和第四条有点冲突需要结合具体的业务场景。常见企业级解决方案UUID优点能够保证独立性程序可以在不同的数据库间迁移效果不受影响。保证生成的ID不仅是表独立的而且是库独立的这点在你想切分数据库的时候尤为重要。缺点性能为题UUID太长通常以36长度的字符串表示,对MySQL索引不利如果作为数据库主键在InnoDB引擎下UUID的无序性可能会引起数据位置频繁变动严重影响性能UUID无业务含义很多需要ID能标识业务含义的地方不使用不满足递增要求信息不安全基于MAC地址生成UUID的算法可能会造成MAC地址泄露这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。不易于存储UUID太长16字节128位通常以36长度的字符串表示很多场景不适用。基于数据库方案利用数据库生成ID是最常见的方案。能够确保ID全数据库唯一。其优缺点如下优点非常简单利用现有数据库系统的功能实现成本小有DBA专业维护。ID号单调自增可以实现一些对ID有特殊要求的业务。缺点:不同数据库语法和实现不同数据库迁移的时候或多数据库版本支持的时候需要处理。在单个数据库或读写分离或一主多从的情况下只有一个主库可以生成。有单点故障的风险。 在性能达不到要求的情况下比较难于扩展。如果涉及多个系统需要合并或者数据迁移会比较麻烦。分表分库的时候会有麻烦。雪花算法41位为时间戳12位为在这一刻能够产生2^12个自增的Id这结合了自增Id的优势同时10位机器ID(dataCenterId 5位和machineId 5位)确保了分布式能够支持1024台节点缺点强依赖时钟,如果主机时间回拨,则会造成重复IDJava实现public class SnowFlake { /** * 开始时间截 (2015-01-01) */ private final long twepoch 1420041600000L; /** * 机器id所占的位数 */ private final long workerIdBits 5L; /** * 数据标识id所占的位数 */ private final long dataCenterIdBits 5L; /** * 支持的最大机器id结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId ~(-1L workerIdBits); /** * 支持的最大数据标识id结果是31 */ private final long maxDataCenterId ~(-1L dataCenterIdBits); /** * 序列在id中占的位数 */ private final long sequenceBits 12L; /** * 机器ID向左移12位 */ private final long workerIdShift sequenceBits; /** * 数据标识id向左移17位(125) */ private final long dataCenterIdShift sequenceBits workerIdBits; /** * 时间截向左移22位(5512) */ private final long timestampLeftShift sequenceBits workerIdBits dataCenterIdBits; /** * * 生成序列的掩码这里为4095 (0b1111111111110xfff4095) */ private final long sequenceMask ~(-1L sequenceBits); /** * 工作机器ID(0~31) */ private volatile long workerId; /** * 数据中心ID(0~31) */ private volatile long dataCenterId; /** * 毫秒内序列(0~4095) */ private volatile long sequence 0L; /** * 上次生成ID的时间截 */ private volatile long lastTimestamp -1L; //Constructors /** * 构造函数 * * param workerId 工作ID (0~31) * param dataCenterId 数据中心ID (0~31) */ public SnowFlake(long workerId, long dataCenterId) { if (workerId maxWorkerId || workerId 0) { throw new IllegalArgumentException(String.format(worker Id cant be greater than %d or less than 0, maxWorkerId)); } if (dataCenterId maxDataCenterId || dataCenterId 0) { throw new IllegalArgumentException(String.format(dataCenter Id cant be greater than %d or less than 0, maxDataCenterId)); } this.workerId workerId; this.dataCenterId dataCenterId; } // Methods /** * 获得下一个ID (该方法是线程安全的) * 如果一个线程反复获取Synchronized锁那么synchronized锁将变成偏向锁。 * return SnowflakeId */ public synchronized long nextId() throws RuntimeException { long timestamp timeGen(); //如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常 if (timestamp lastTimestamp) { throw new RuntimeException((String.format(Clock moved backwards. Refusing to generate id for %d milliseconds, lastTimestamp - timestamp))); } //如果是同一时间生成的则进行毫秒内序列 if (lastTimestamp timestamp) { sequence (sequence 1) sequenceMask; //毫秒内序列溢出 if (sequence 0) { //阻塞到下一个毫秒,获得新的时间戳 timestamp tilNextMillis(lastTimestamp); } } //时间戳改变毫秒内序列重置 else { sequence 0L; } //上次生成ID的时间截 lastTimestamp timestamp; //移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) timestampLeftShift) | (dataCenterId dataCenterIdShift) | (workerId workerIdShift) | sequence; } /** * 阻塞到下一个毫秒直到获得新的时间戳 * * param lastTimestamp 上次生成ID的时间截 * return 当前时间戳 */ private long tilNextMillis(long lastTimestamp) { long timestamp timeGen(); while (timestamp lastTimestamp) { timestamp timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * * return 当前时间(毫秒) */ private long timeGen() { return System.currentTimeMillis(); } }为什么叫雪花算法呢私以为众所周知世界上没有一对相同的雪花而雪花形成的过程中复杂的环境条件则对应了雪花算法中的机器ID,时间戳在现实层面上能够精确到普朗克时间10^-43s这样时间戳的长度也得以保证。