分布式Id生成器

Id生成的几种方式

  • 数据库自增
    • 优势
      • 简单,无需额外操作
      • 保持定长自增
      • 保持单表唯一性
    • 劣势
      • 主键生成依赖数据库,高并发下会造成数据库服务器压力较大
      • 水平扩展困难,在分布式数据库环境下,无法保证唯一性
  • UUID(GUID)

    • 优势
      • 本地生成,无需远程调用(无需网络通信)
      • 全局唯一
      • 水平扩展好
    • 劣势
      • ID 128 bits,占用空间大
      • 字符串类型,索引效率低
      • 无法保证趋势递增
  • 时间戳

  • Twitter Snowflake 雪花算法

    • 优势
      • 本地生产,无需远程调用(无需网络通信)
      • 单机每秒可生成400w个ID
      • ID 64 bits,占用空间小
      • long类型,对索引效率有提升
      • 趋势递增
    • 劣势
      • 时间回拨问题
      • 集群部署时,集群内机器时间同步问题

基于雪花算法的分布式Id生成器实现

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
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import java.util.Calendar;

/**
* 该生成器采用 Twitter Snowflake 算法实现,生成 64 Bits 的 Long 型编号
* <pre>
* 1 bit 41 bits 10 bits 12 bits
* sign bit 时间戳 工作ID 序列号ID
* </pre>
*
* @author YL
*/
public final class SnowflakeKeyGenerator implements KeyGenerator {
/**
* 基准时间:时间偏移量
*/
private static final long EPOCH;

static {
// 从2017年01月01日零点开始
Calendar calendar = Calendar.getInstance();
calendar.set(2017, Calendar.JANUARY, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
EPOCH = calendar.getTimeInMillis();
}

/**
* 序列号ID位数
*/
private static final long SEQUENCE_BITS = 12L;
/**
* 工作ID位数
*/
private static final long WORKER_ID_BITS = 10L;

/**
* 序列号ID最大值
*/
private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;
/**
* 工作ID最大值
*/
private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;

/**
* 工作ID左移12位
*/
private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;
/**
* 时间戳左移22数
*/
private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

/**
* 序列号ID
*/
private long sequence;
/**
* 序列号ID偏移量
*/
private byte sequenceOffset;
/**
* 最后生成编号时间戳,单位:毫秒
*/
private long lastTime;

/**
* 工作ID
*/
private long workerId;

public SnowflakeKeyGenerator(long workerId) {
if (workerId >= WORKER_ID_MAX_VALUE || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",
WORKER_ID_MAX_VALUE));
}
this.workerId = workerId;
}

/**
* 生成 ID
*
* @return 返回@{@link Long}类型的Id
*/
@Override
public synchronized long nextId() {
// 保证当前时间大于最后时间。时间回退会导致产生重复id
long currentMillis = System.currentTimeMillis();
if (currentMillis < this.lastTime) {
throw new IllegalArgumentException(String.format("clock moved backwards.Refusing to generate id for %d " +
"milliseconds",
(this.lastTime - currentMillis)));
}

// 获取序列号
if (this.lastTime == currentMillis) {
// 当获得序号超过最大值时,归0,并去获得新的时间
if (0L == (this.sequence = ++this.sequence & SEQUENCE_MASK)) {
currentMillis = tailNextMillis(currentMillis);
}
} else {
// 1、在跨毫秒时,序列号总是归0,导致序列号为0的ID较多,导致生成的ID取模后不均匀
// this.sequence = 0;
// 2、序列号取[0-9]之间的随机数,可以初步解决【1】中的问题,也会导致ID取模不均匀
// this.sequence = new SecureRandom().nextInt(10);
// 3、交替使用[0-1]
this.sequence = vibrateSequenceOffset();
}
// 设置最后时间戳
this.lastTime = currentMillis;
// 生成编号
return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS)
| (this.workerId << WORKER_ID_LEFT_SHIFT_BITS)
| this.sequence;
}

/**
* 不停获得时间,直到大于最后时间
* <p>
* 从 Snowflake 的官方文档 (https://github.com/twitter/snowflake/#system-clock-dependency) 中也可以看到, 它明确要求 "You should
* use NTP to keep your system clock accurate". 而且最好把 NTP 配置成不会向后调整的模式. 也就是说, NTP 纠正时间时, 不会向后回拨机器时钟.
* ntpd 和 ntpdate 的区别,使用 ntpd 影响不大
* todo 如果时间回拨,会导致这个逻辑等待,等待时间可能会很长
* </p>
*
* @param lastTime 最后时间
*
* @return 时间
*/
private long tailNextMillis(final long lastTime) {
long time = System.currentTimeMillis();
while (time <= lastTime) {
time = System.currentTimeMillis();
}
return time;
}

/**
* 只会交替返回0和1
*/
private byte vibrateSequenceOffset() {
this.sequenceOffset = (byte) (~this.sequenceOffset & 1);
return this.sequenceOffset;
}
}
  • 本文作者: forever杨
  • 本文链接: https://blog.yl-online.top/posts/f9490f40.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。如果文章内容对你有用,请记录到你的笔记中。本博客站点随时会停止服务,请不要收藏、转载!