Snowflake IDs 或 snowflakes 是一种应用于分布式系统的 id 形式,由 Twitter 创造并用于标记其内容 (tweets). Discard, Instagram 等互联网公司也采用了修改后的雪花id。

(wikipedia 对 Snowflake ID 的说明)


原理说明

雪花id的原理非常简单。一个 long 占用 64 digits, 将之划分成四个区段,分别存放不同的信息:

区段一

长度为 1 digit 且固定为 0,用于兼容无法使用 unsigned long 的语言(如 Java), 否则 id 会显示成负数。

区段二 - timestamp

长度为 41 digits, 用于记录时间戳 timestamp。需要注意的是,该时间戳的起点不是通常的 1970-01-01,而是自定义的某个时间点。

例如:设置时间起点为 2010-01-01 00:00:00:0,则 timestamp=1000 时,对应的时间点为 2010-01-01 00:00:01:0

使用 41 digits, 可表示约 69 年,即基于起点时间的 69 年以内可用:

1
2
3
4
5
6
7
// 验证 41 digits 的最大值为多少年
long timestamp = ~(-1L << 41);
long days = TimeUnit.DAYS.convert(timestamp, TimeUnit.MILLISECONDS);
long years = days / 365;

System.out.printf("days: %d, years: %d", days, years);
// days: 25451, years: 69

区段三 - instance

10 digits 用于表示实例的编号或索引值,即可用于区分 2^10 = 1024 个雪花id生成器的实例。理论上可部署多台服务器、每台服务器存在多个实例,只要总数在 1024 以内即可,这也是 Snowflake ID 支持分布式系统的关键。

网络上有些资料把 instance 区段再拆分开 datacenter 和 worker,实际上原理都是一样的,就不展开了。

区段四 - sequence

12 digits 用于表示序列号,且与 timestamp 相应。即理论上同一毫秒内,可生成 2^12 = 4096 个 sequence. (如果超出该数量,则等待直到下一毫秒)

缺点

个人认为最大缺点应该是不能直接反映生成日期。在业务上,将日期反映在订单编号上,对于运维来说应该是比较便利的。

定制版

基于以上原理,其他人均可修改区间定义以实现自己的定制版。例如:

  • 把首个 digit 也加入有意义的区间
  • 增加或减少 instance 的区间长度
  • 缩减部分区间长度,用于自定义的区间,如版本号、业务模块索引等

代码示例

参考了网络资料,并作出部分优化、完善注释。

1
2
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
public class SnowflakeId {
// 2018-01-01 00:00:00:0; 起始时间点,可随意设置
private static final long SP_EPOCH = 1514736000000;

/* 各区间的长度 */
private static final int TOTAL_DIGITS = 64;
private static final int TIMESTAMP_DIGITS = 41;
private static final int INSTANCE_DIGITS = 10;
private static final int SEQUENCE_DIGITS = 12;

/**
* "-1L"在补码中即为64个1,左移n位后再取反("~"操作符),即可得到n个1的掩码,同时也是该区间的最大值。
* 以 instance 区间为例,"-1L"左移10位后再取反,即可得到 "11 1111 1111" (及左侧的54个 leading zeros)
*
* 网络资料中大多是左移后再与-1进行异或操作,如 INSTANCE_MASK = -1L ^ (-1L << INSTANCE_DIGITS) 与直接取反
* 的方式是完全等价的。
*/
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_DIGITS);
private static final long INSTANCE_MASK = ~(-1L << INSTANCE_DIGITS);
private static final long TIMESTAMP_MASK = ~(-1L << TIMESTAMP_DIGITS);

private static final int TIMESTAMP_MOVEMENT = SEQUENCE_DIGITS + INSTANCE_DIGITS;
/**
* 当前 instance 在 Snowflake ID 中的值是在实例创建时就已经确定好,后续无需反复操作
*/
private final int instance;

private long timestampMarker;
private long sequence;

/**
* 创建 Snowflake ID 实例
* @param instanceIndex 实例索引值,合法范围为[0, 1023]
*/
public SnowflakeId(int instanceIndex) {
if (instanceIndex < 0 || instanceIndex > INSTANCE_MASK) {
throw new IllegalArgumentException("Illegal instanceIndex: less than 0 or greater than " + INSTANCE_MASK);
}
this.instance = instanceIndex << SEQUENCE_DIGITS;
}

/**
* 生成雪花id, thread-safe
*/
public synchronized long getNextId() {
long currentTimestamp = this.getTimestamp();
if (currentTimestamp < timestampMarker) {
throw new IllegalStateException("Clock move backwards.");
}

// 同一毫秒之内,理论上最多可生成4096个id
if (currentTimestamp == timestampMarker) {
sequence = (++sequence) & SEQUENCE_MASK;
if (sequence == 0) {
// sequence自增后溢出,故等待直到毫秒数发生变更
while (currentTimestamp <= timestampMarker) {
currentTimestamp = this.getTimestamp();
}
}
} else {
sequence = 0;
}
timestampMarker = currentTimestamp;
return (currentTimestamp << TIMESTAMP_MOVEMENT) | instance | sequence;
}

/**
* 获取当前时间点距离起始时间点的毫秒数
*/
private long getTimestamp() {
return System.currentTimeMillis() - SP_EPOCH;
}

}