关于分布式ID

此文介绍一些分布式 ID 的生成策略,重点在最后一个版块。

一、背景

    唯一 ID,每个人都不陌生,因为在很多场景下我们都会接触到,比如:

        — 用户唯一标识

        — 订单唯一标识

        — 商品唯一标示

    唯一 ID 的生成方式是有很多的,而且在不同的业务场景下,会有不同的生成需求,即没有任何一种 ID 的生成方式是最适合所有业务场景的。

    所以说,脱离业务来谈的技术,都是耍流氓。

    下文会分析一些分布式 ID 的生成方式以供参考。


</p>

二、分布式 ID —— UUID

    UUID(Universally Unique Identifier)是通用的唯一识别码,开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。

    UUID 是由128位二进制组成,一般转换成十六进制,然后用 String 表示。

    在 java 中有个 UUID 类,在他的注释中我们看见这里有 4 种不同的UUID的生成策略:

        UUID-01.png

        ① 基于时间的; ② 基于 DCE 安全的; ③ 基于名称空间的; ④ 基于随机数的

        一般我们默认使用第四种,

    另外,我们如果想研究更多的 UUID 的算法,附上传送门:http://www.ietf.org/rfc/rfc4122.txt

        UUID-02.png

  • 优点: 1、无序,即生成规则是无序的 2、本地生成,性能较高

  • 缺点: 1、无序,因为无序带来的弊端是不能生成连续的数字 2、32 位的十六进制,只能使用 String 来存储,空间占用相对会多一点


三、分布式 ID —— 数据库主键自增

    数据库主键自增比较简单,使用其实很方便,场景也相对比较广。

    使用时只需要将字段设置为主键即可。

  • 优点: 1、简单,方便 2、排序和分页都很方便

  • 缺点: 1、并发性不好,受限于数据库性能 2、稳定性不高,受限于数据库的服务,需担心数据库宕机 3、分库分表时会有问题,需要定制化开发区解决 4、业务量太明显,因为是自增的数字,所以业务量很容易被研究

    所以,对于一些简单的业务场景,比如内部系统、to-B 的系统等,业务量并不高,使用数据库主键自增的方案为最佳。




四、分布式 ID —— redis

    redis 是单线程的,可以充分保证原子性

    redis 中的俩命令一用便会:Incr 和 IncrBy

  • 优点: 1、性能比数据库好 2、能满足自增的序列号

  • 缺点: 1、由于是基于内存的数据库,所以有可能会存在数据丢失的情况,会导致 ID 重复 2、稳定性需担心,因为完全依赖于 redis



五、分布式 ID —— 雪花算法(Snowflake)

    Snowflake 是 Twitter 提出来的一个算法,其目的是生成一个 64bit 的整数,如下图:

        UUID-03.png

    说明:

        1、1bit:一般是符号位,不做处理

        2、41bit:用来记录时间戳,但是最多只可以记录69年(如果这个系统能用69年,估计已经被重构好多次了)

        3、10bit:10bit用来记录机器ID,总共可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID

        4、12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。

    当然,上面的说明只是一个最常规的使用说明,在我么实际使用的时候,需要根据业务来进行酌情调整,但是总长度不变(64bit),比如:

        案例一:当我们的业务服务器位于北、上、广、深四地,则可以将 10bit 的工作机器 id 调整为 3+7 模式,即 3 位(最多看标识8)用于标识机房位置,7位(最多标识128)用于标识机器 id。

        案例二:当我们的业务量没那么大的时候,则可以将最后的 12bit 的序列号再进行划分,调整为 10 + 2 模式,即 10 位(最多标识1024)用于标识序列号,2 位用于扩展。

    雪花算法生成的 ID 有点多多,满足了 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
public&nbsp;class&nbsp;IdWorker{
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;workerId;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;datacenterId;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;sequence&nbsp;=&nbsp;0;
&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;2018/10/21日,从此时开始计算,可以用到2087年
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;twepoch&nbsp;=&nbsp;1540126254592L;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;workerIdBits&nbsp;=&nbsp;5L;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;datacenterIdBits&nbsp;=&nbsp;5L;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;sequenceBits&nbsp;=&nbsp;12L;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;workerIdShift&nbsp;=&nbsp;sequenceBits;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;datacenterIdShift&nbsp;=&nbsp;sequenceBits&nbsp;+&nbsp;workerIdBits;
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;timestampLeftShift&nbsp;=&nbsp;sequenceBits&nbsp;+&nbsp;workerIdBits&nbsp;+&nbsp;datacenterIdBits;
&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;得到0000000000000000000000000000000000000000000000000000111111111111
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;sequenceMask&nbsp;=&nbsp;~(-1L&nbsp;<<&nbsp;sequenceBits);
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;lastTimestamp&nbsp;=&nbsp;-1L;
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;IdWorker(long&nbsp;workerId,&nbsp;long&nbsp;datacenterId){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.workerId&nbsp;=&nbsp;workerId;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.datacenterId&nbsp;=&nbsp;datacenterId;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;synchronized&nbsp;long&nbsp;nextId()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;timestamp&nbsp;=&nbsp;timeGen();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;时间回拨,抛出异常
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(timestamp&nbsp;<&nbsp;lastTimestamp)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.err.printf("clock&nbsp;is&nbsp;moving&nbsp;backwards.&nbsp;&nbsp;Rejecting&nbsp;requests&nbsp;until&nbsp;%d.",&nbsp;lastTimestamp);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;RuntimeException(String.format("Clock&nbsp;moved&nbsp;backwards.&nbsp;&nbsp;Refusing&nbsp;to&nbsp;generate&nbsp;id&nbsp;for&nbsp;%d&nbsp;milliseconds",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastTimestamp&nbsp;-&nbsp;timestamp));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;时间回拨
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;伪代码
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*if&nbsp;(&nbsp;timestamp&nbsp;<&nbsp;lastTimestamp)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(lastTimestamp&nbsp;-&nbsp;timestamp&nbsp;<=&nbsp;5)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;时间在&nbsp;5ms&nbsp;以内,则直接等待&nbsp;5ms,让其追上
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(5));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timestamp&nbsp;=&nbsp;timeGen();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;如果还小,则利用扩展字段(2位扩展字段最多支持3次回拨)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(timestamp&nbsp;<&nbsp;lastTimestamp)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;扩展字段&nbsp;+&nbsp;1
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;extension&nbsp;+=&nbsp;1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(extension&nbsp;>&nbsp;maxExtension)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;RuntimeException(String.format("Clock&nbsp;moved&nbsp;backwards.&nbsp;&nbsp;Refusing&nbsp;to&nbsp;generate&nbsp;id&nbsp;for&nbsp;%d&nbsp;milliseconds",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastTimestamp&nbsp;-&nbsp;timestamp));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;扩展字段&nbsp;+&nbsp;1
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;extension&nbsp;+=&nbsp;1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(extension&nbsp;>&nbsp;maxExtension)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;RuntimeException(String.format("Clock&nbsp;moved&nbsp;backwards.&nbsp;&nbsp;Refusing&nbsp;to&nbsp;generate&nbsp;id&nbsp;for&nbsp;%d&nbsp;milliseconds",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastTimestamp&nbsp;-&nbsp;timestamp));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(lastTimestamp&nbsp;==&nbsp;timestamp)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sequence&nbsp;=&nbsp;(sequence&nbsp;+&nbsp;1)&nbsp;&&nbsp;sequenceMask;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(sequence&nbsp;==&nbsp;0)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timestamp&nbsp;=&nbsp;tilNextMillis(lastTimestamp);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sequence&nbsp;=&nbsp;0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastTimestamp&nbsp;=&nbsp;timest
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;((timestamp&nbsp;-&nbsp;twepoch)&nbsp;<<&nbsp;timestampLeftShift)&nbsp;|
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(datacenterId&nbsp;<<&nbsp;datacenterIdShift)&nbsp;|
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(workerId&nbsp;<<&nbsp;workerIdShift)&nbsp;|
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sequence;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;当前ms已经满了
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;lastTimestamp
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;tilNextMillis(long&nbsp;lastTimestamp)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;timestamp&nbsp;=&nbsp;timeGen();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(timestamp&nbsp;<=&nbsp;lastTimestamp)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timestamp&nbsp;=&nbsp;timeGen();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;timest
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;long&nbsp;timeGen(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;System.currentTimeMillis();
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IdWorker&nbsp;worker&nbsp;=&nbsp;new&nbsp;IdWorker(1,1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;<&nbsp;30;&nbsp;i++)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(worker.nextId());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}












------ 本文结束 感谢阅读 ------
0%