分布式ID方案SnowFlake雪花算法分析
【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
1、算法
SnowFlake算法生成的数据组成结构如下:
在java中用long类型标识,共64位(每部分用-分开): 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00
- 1位标识,0表示正数。
- 41位时间戳,当前时间的毫秒减去开始时间的毫秒数。可用 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。
- 5位数据中心标识,可支持(1L << 5) = 32个数据中心。
- 5位机器标识,每个数据中心可支持(1L << 5) = 32个机器标识。
- 12位序列号,每个节点每一毫秒支持(1L << 12) = 4096个序列号。
2、Java版本实现
/** * 雪花算法<br> * 在java中用long类型标识,共64位(每部分用-分开):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00<br> * 1位标识,0表示正数。<br> * 41位时间戳,当前时间的毫秒减去开始时间的毫秒数。可用 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。<br> * 5位数据中心标识,可支持(1L << 5) = 32个数据中心。<br> * 5位机器标识,每个数据中心可支持(1L << 5) = 32个机器标识。<br> * 12位序列号,每个节点每一毫秒支持(1L << 12) = 4096个序列号。<br> */ public class SnowflakeIdWorker { /** * 机器标识 */ private long workerId; /** * 数据中心标识 */ private long dataCenterId; /** * 序列号 */ private long sequence; /** * 机器标识占用5位 */ private long workerIdBits = 5L; /** * 数据中心标识占用5位 */ private long dataCenterIdBits = 5L; /** * 12位序列号 */ private long sequenceBits = 12L; /** * 12位序列号支持的最大正整数 * ....... 00001111 11111111 * 2^12-1 = 4095 */ private long sequenceMask = ~(-1L << sequenceBits); /** * The Worker id shift. * 12位 */ private long workerIdShift = sequenceBits; /** * The Data center id shift. * 12 + 5 = 17位 */ private long dataCenterIdShift = sequenceBits + workerIdBits; /** * The Timestamp shift. * 12 + 5 + 5 = 22位 */ private long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits; /** * 开始时间戳毫秒 */ private long startEpoch = 29055616000L; /** * The Last timestamp. */ private long lastTimestamp = -1L; public SnowflakeIdWorker(long workerId, long dataCenterId, long sequence) { // 检查workerId是否正常 /* 机器标识最多支持的最大正整数 -1的补码: 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 -1 左移 5 位,高位溢出,低位补0: 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000 取反: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111 转10进制: 16 + 8 + 4 + 2 + 1 = 31 */ long maxWorkerId = ~(-1L << workerIdBits); if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("工作Id不能大于%d或小于0", maxWorkerId)); } /* 数据中心最多支持的最大正整数31 */ long maxDataCenterId = ~(-1L << dataCenterIdBits); if (dataCenterId > maxDataCenterId || dataCenterId < 0) { throw new IllegalArgumentException(String.format("数据中心Id不能大于%d或小于0", maxDataCenterId)); } this.workerId = workerId; this.dataCenterId = dataCenterId; this.sequence = sequence; } private synchronized long nextId() { //获取当前时间毫秒数 long timestamp = timeGen(); //如果当前时间毫秒数小于上一次的时间戳 if (timestamp < lastTimestamp) { System.err.printf("时钟发生回调,拒绝生成ID,直到: %d.", lastTimestamp); throw new RuntimeException(String.format("时钟发生回调, 拒绝为 %d 毫秒生成ID。", lastTimestamp - timestamp)); } //当前时间毫秒数与上次时间戳相同,增加序列号 if (lastTimestamp == timestamp) { //假设sequence=4095 //(4095 + 1) & 4095 //4096: ....... 00010000 00000000 //4095: ....... 00001111 11111111 // ....... 00000000 00000000 //最终sequence为0,即sequence发生溢出。 sequence = (sequence + 1) & sequenceMask;//如果发生序列号为0,即当前毫秒数的序列号已经溢出,则借用下一毫秒的时间戳 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { //当前毫秒数大于上次的时间戳,序列号为0 sequence = 0; } //更新 lastTimestamp = timestamp; //生成ID算法,左移几位,则后面加几个0。 //1、当前时间的毫秒数-开始时间的毫秒数,结果左移22位 // 假设:timestamp - startEpoch = 1 // 二进制: // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 左移22位: // 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000 //2、dataCenterId左移17位 // 假设:dataCenterId = 1 // 二进制: // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 左移17位: // 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000 //3、workerId左移12位 // 假设:workerId = 1 // 二进制: // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 左移12位: // 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000 //4、最后的所有结果按位`或` //假设:sequence = 1 //00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000 //00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000 //00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000 //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 //00000000 00000000 00000000 00000000 00000000 01000010 00010000 00000001 //结果: 0 - 0000000 00000000 00000000 00000000 00000000 01 - 00001 - 00001 - 0000 00000001 return ((timestamp - startEpoch) << timestampShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; } /** * 获取下一秒 * * @param lastTimestamp the last timestamp * @return the long */ private long tilNextMillis(long lastTimestamp) { //获取当前毫秒数 long timestamp = timeGen(); //只要当前的毫秒数小于上次的时间戳,就一直循环,大于上次时间戳 while (timestamp <= lastTimestamp) { //获取当前毫秒数 timestamp = timeGen(); } return timestamp; } /** * 获取当前毫秒数 * * @return the long */ private long timeGen() { return System.currentTimeMillis(); } public static void main(String[] args) { SnowflakeIdWorker worker = new SnowflakeIdWorker(1, 1, 1); for (int i = 0; i < 10000; i++) { long id = worker.nextId(); System.out.println(id); System.out.println(Long.toString(id).length()); System.out.println(Long.toBinaryString(id)); System.out.println(Long.toBinaryString(id).length()); } } }
3、难点
Tips: 左移几位,则后面加几个0。
3.1、计算机器标识最多支持的最大正整数
private long workerIdBits = 5L; long maxWorkerId = ~(-1L << workerIdBits);
计算过程:
- -1的补码:<br> 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
- -1 左移 5 位,高位溢出,低位补0:<br> 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
- 取反:<br> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111
- 转10进制:<br> 16 + 8 + 4 + 2 + 1 = 31
3.2、sequence溢出处理
sequence = (sequence + 1) & sequenceMask;
计算过程:<br> 假设sequence=4095:<br>
- (4095 + 1) & 4095
- 4096: ....... 00010000 00000000
- 4095: ....... 00001111 11111111
- 按位与 ....... 00000000 00000000
- 最终sequence为0,即sequence发生溢出。
3.3、ID计算
((timestamp - startEpoch) << timestampShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence
计算过程:
- 当前时间的毫秒数-开始时间的毫秒数,结果左移22位
假设:timestamp - startEpoch = 1<br> 二进制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br> 左移22位: 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000<br>
- dataCenterId左移17位
假设:dataCenterId = 1<br> 二进制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br> 左移17位: 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000<br>
- workerId左移12位
假设:workerId = 1<br> 二进制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br> 左移12位: 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000<br>
- 最后的所有结果按位
或
假设:sequence = 1<br> 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000<br> 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000<br> 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000<br> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br>
00000000 00000000 00000000 00000000 00000000 01000010 00010000 00000001<br>
- 结果:
0 - 0000000 00000000 00000000 00000000 00000000 01 - 00001 - 00001 - 0000 00000001<br> 符合SnowFlake算法数据组成结构。
参考
理解分布式id生成算法SnowFlake Twitter雪花算法SnowFlake算法的java实现
- Twitter-Snowflake(64位分布式ID算法)分析与JAVA实现
- Twitter的分布式雪花算法 SnowFlake 每秒自增生成26个万个可排序的ID (Java版)
- SnowFlake雪花算法生成分布式id
- Twitter的分布式自增ID算法Snowflake实现分析及其Java、Php和Python版
- 分布式全局ID的Twitter的Snowflake(雪花算法)
- Twitter的分布式自增ID雪花算法snowflake (Java版)
- [转] Twitter的分布式自增ID算法Snowflake实现分析及其Java、Php和Python版
- 分布式ID生成器 snowflake(雪花)算法
- Twitter的分布式雪花算法 SnowFlake 每秒自增生成26个万个可排序的ID (Java版)
- Twitter的分布式自增ID算法Snowflake实现分析及其Java、Php和Python版
- 理解分布式id生成算法SnowFlake
- Twitter的分布式自增ID算法snowflake
- Twitter的雪花算法(snowflake)自增ID
- Twitter的雪花算法(snowflake)自增ID
- SnowFlake --- 分布式id生成算法
- Twitter的分布式雪花算法 SnowFlake
- 分布式ID自增算法 Snowflake
- Twitter的分布式自增ID算法snowflake (Java版)
- SnowFlake --- 分布式id生成算法工具类
- Twitter的分布式自增ID算法snowflake