分布式ID解决方案!

数据库号段模式

号段模式为从数据库批量的获取自增ID,每次从数据库取出一个号段范围。

例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。

  • biz_type :代表不同业务类型

  • max_id :当前最大的可用id

  • step :代表号段的长度

  • version :是一个乐观锁,每次都更新version,保证并发时数据的正确性

1
2
3
4
5
6
7
8
CREATE TABLE id_generator (
id int(10) NOT NULL,
max_id bigint(20) NOT NULL COMMENT '当前最大id',
step int(20) NOT NULL COMMENT '号段的布长',
biz_type int(20) NOT NULL COMMENT '业务类型',
version int(20) NOT NULL COMMENT '版本号',
PRIMARY KEY (`id`)
)

等这批号段ID用完,再次向数据库申请新号段,对max_id字段做一次update操作,update max_id= max_id + step

update成功则说明新号段获取成功,新的号段范围是(max_id ,max_id +step]

1
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX

由于多业务端可能同时操作,所以采用版本号version乐观锁方式更新。

这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。

雪花SnowFlake算法

雪花算法生成64位的二进制正整数,然后转换成10进制的数。

64位二进制数由如下部分组成:

image-20231012141356009

1位标识符:始终是0

41位时间戳

存储时间截的差值(当前时间截 - 开始时间截 )得到的值,这里的的开始时间截,一般是ID生成器开始使用的时间,由程序来指定的

10位机器标识码:可以部署在1024个节点,如果机器分机房(IDC)部署,这10位可以由 5位机房ID + 5位机器ID 组成

12位序列:毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号

优点:

每秒能够产生409.6万个ID,性能快

时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序递增

灵活度高,可以根据业务需求,调整bit位的划分,满足不同的需求

缺点:

依赖机器的时钟,如果服务器时钟回拨,会导致重复ID生成

优化思路:

思路就是在系统中监控每台时钟是否回拨?如果回拨了,作一下调整就行了。

持久化节点的作用:就是保存各个ID服务的节点信息,并保存时钟

image-20231012141726598

整体流程:

1、ID服务启动,注册到Zookeeper。

2、检查是否之前注册过,如果没有注册过,就把此服务注册到Zookeeper中。

3、节点的信息利用顺序节点,保证唯一。

  • ID服务把此生成的顺序ID当作workID,保存到本地文件系统中,这样以后再次启动时,就拿着此workId到 zk 上面去查找,是否之前注册过。

4、如果之前没有注册过,生成了ID服务的zk节点,并把本地的时钟保存到此zk节点中。

  • ID服务会每隔3秒,把本地时钟上报到zk节点上面。

5、如果在注册的时候,发现已经注册了,要去比较一下本地时钟 和 zk 节点上面的时钟。

  • 如果本地时钟 大于 zk 上的,表示正常,启动成功。
  • 否则表示本地的时钟发生了回拨,启动不成功,启动报警机制。

算法实现:

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
/**
* Twitter的SnowFlake算法,使用SnowFlake算法生成一个整数,然后转化为62进制变成一个短地址URL
*
* https://github.com/beyondfengyu/SnowFlake
*/
public class SnowFlakeShortUrl {

/**
* 起始的时间戳
*/
private final static long START_TIMESTAMP = 1480166465631L;

/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATA_CENTER_BIT = 5; //数据中心占用的位数

/**
* 每一部分的最大值
*/
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);

/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

private long dataCenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastTimeStamp = -1L; //上一次时间戳

private long getNextMill() {
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp) {
mill = getNewTimeStamp();
}
return mill;
}

private long getNewTimeStamp() {
return System.currentTimeMillis();
}

/**
* 根据指定的数据中心ID和机器标志ID生成指定的序列号
*
* @param dataCenterId 数据中心ID
* @param machineId 机器标志ID
*/
public SnowFlakeShortUrl(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}

/**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currTimeStamp = getNewTimeStamp();
if (currTimeStamp < lastTimeStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}

if (currTimeStamp == lastTimeStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currTimeStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}

lastTimeStamp = currTimeStamp;

return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}

public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);

for (int i = 0; i < (1 << 4); i++) {
//10进制
System.out.println(snowFlake.nextId());
}
}
}

百度(uid-generator)

https://github.com/baidu/uid-generator

美团(Leaf)

https://link.zhihu.com/?target=https%3A//github.com/Meituan-Dianping/Leaf

滴滴(Tinyid)

https://github.com/didi/tinyid