引言
我们在进行数据存储时,往往会因为数据量过大导致一个表不足以放下所有数据。这时我们就会进行分库分表,而如果我们使用水平分表的话,就会遇到一个问题:ID如何保证唯一?
1.我们可以使用ID的起始位置不同,通过步长等于表数,做到ID唯一,但终究是局限的,万一还要增加表呢?
2.我们可以使用UUID做唯一ID,但我们一般使用id都是有序的,这样明显用着不舒服。
所以在这样的场景下产生了新的算法:
-
"雪花算法":ID生成策略,按照时间顺序生成分布式唯一ID。
正文
-
在项目的application文件中有这样的配置来生成id。
-
这里也可以自己设置work-id,datacenter-id,但记得需要同时赋值(具体可以看源码中的生成过程)
-
深入源码中,我们发现,原来ID是按照这样的结构来实现的:
-
可以看到,这边源码显示,是使用了|运算,将我们所需的四个部分拼接在一起。
-
时间戳往左偏移22位,留了22位给节点编号和序列号,节点编号中有datacenterId+workerId,序列号占用12位。那么节点编号中的两个值各占5位。
-
41+10+12+1 = 64,刚好为64,那么这就是雪花算法。
具体值含义:
-
符号位:固定为0
-
时间戳:占用41位,是从固定时间点到当前时间的毫秒数,转换成二进制后是ID的第2~42位
-
节点编号:占用10位,表示不同节点,对于同一个程序来说,可以固定写死一个节点编号,转换成二进制后是ID的第43~52位
-
序列号:占用12位,表示同一节点在同一毫秒内的不同序列号,每个序列号都会自增,最大值是4095,转换成二进制后是ID的第53~64位。
-
可以看到这两个值都是系统按照ip生成的,所以可以代表某个集群ip的唯一标识。
检验过程
-
我们可以拿目前的ID,看看他的时间戳是否正确。(*是我为了不暴露ip代替了真实数据)
ID:1739202***********
二进制:0001100000100010111000110101001000100110010000*******************
-
我们取它的时间戳位置,2-41位
0|00110000010001011100011010100100010011001|0000000000000000000000
-
把这串二进制转换为10进制
->1739202348086984704
-
转换代码
timestamp - 1288834974657L << 22 = ?
timestamp - 1288834974657L = ? >> 22
timestamp = ? >> 22 + 1288834974657L
-
那么,这个"?"就是刚刚转换的值,将它算出来
-
OK,将后三位去掉,只取秒级的时间戳
-
得出的时间完美符合数据创建时间,但也许id中的更加准确,到了毫秒级
-
同理,按照源代码来也可以得出workID,IP等信息!