分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。
理论说明
- 能满足高并发分布式系统环境下ID不重复
- 基于时间戳,可以保证基本有序递增(有些业务场景对这个又要求)
- 不依赖第三方的库或者中间件
- 生成效率极高

- 1位,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
- 41位,用来记录时间戳(毫秒)。41位可以表示2^41−1个数字,因为要保证自增,不允许回调时间!
- 10位,用来记录工作机器ID。 可以部署在2^10=1024个节点
- 12位,序列号,用来记录同毫秒内产生的不同id。 12位可以表示的最大正整数是2^12−1=4095,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号
额外的,可以在这41+10+12位中,取出一些连续的位,设置为特别需求的标志位,如业务ID啥的
| 12
 3
 4
 5
 6
 7
 8
 
 |  1L == 0000 0000 0000 0001-1L == 1111 1111 1111 1111,-1 的补码 为 1 的 反码 +1
 -1L << MACHINE_SHIFT:-1 左移 10 位:高位舍弃,低位用 0 补齐,为 1111 1100 0000 0000
 则:
 1111 1111 1111 1111
 ^ 1111 1100 0000 0000
 ---------------------
 = 0000 0011 1111 1111
 
 | 
补充说明
- 当进入下一毫秒时,序列号不应直接置0,因为如果一个业务量恰好1ms生成一次,那出现的结果均为偶数,不利于分库分表,可以根据时间奇偶性,来设定 0 或 1 来改善奇偶性
- 项目的 START_STAMP 是不允许会退的,因为要保证 ID 自增且唯一,如果回退了,有可能会出现重复 ID,当发现时间回退时,应抛出异常
- 当同一毫秒生成数达到最大值时,不能继续生成,否则序列号必然重复,需要自旋等待下一毫秒
Java 代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 
 | package vip.housir;
 
 
 
 public class SnowFlake {
 
 
 
 
 
 private final static long START_STAMP = 1600074513000L;
 
 
 
 
 private final static int SERIAL_LEN = 12;
 
 
 
 
 private final static int MACHINE_LEN = 10;
 
 
 
 
 
 
 
 private final static long MAX_MACHINE = -1L ^ (-1L << MACHINE_LEN);
 
 
 
 
 private final static long MAX_SERIAL = -1L ^ (-1L << SERIAL_LEN);
 
 
 
 
 private final static int MACHINE_SHIFT = SERIAL_LEN;
 
 
 
 
 private final static int TIMESTAMP_SHIFT = MACHINE_LEN + SERIAL_LEN;
 
 private long serialNum = 0L;
 
 private long lastTimeStamp = -1L;
 
 private final int machineId;
 
 public SnowFlake(int machineId) {
 
 if (machineId > MAX_MACHINE) {
 throw new RuntimeException("Machine ID out of the range, MAX_MACHINE: " + MAX_MACHINE);
 }
 this.machineId = machineId;
 }
 
 
 
 
 
 
 public synchronized long generateId() {
 
 long curStamp = getNewStamp();
 
 
 if (curStamp < lastTimeStamp) {
 throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
 }
 
 
 if (curStamp == lastTimeStamp) {
 
 serialNum = (serialNum + 1) & MAX_SERIAL;
 
 if (serialNum == 0L) {
 
 curStamp = waitNextMill();
 }
 } else {
 
 serialNum = curStamp & 1;
 }
 
 
 lastTimeStamp = curStamp;
 
 return ((curStamp - START_STAMP) << TIMESTAMP_SHIFT)
 | (machineId << MACHINE_SHIFT)
 | serialNum;
 }
 
 private long waitNextMill() {
 long mill = getNewStamp();
 
 while (mill <= lastTimeStamp) {
 mill = getNewStamp();
 }
 return mill;
 }
 
 private long getNewStamp() {
 return System.currentTimeMillis();
 }
 }
 
 |