雪花算法
1.是什么
(SnowFlake)由Twitter创造的,是一个唯一ID生成算法,且具备有序性和可扩展性.
2.怎么组成
生成出的ID是一个64bits的整数,其中时间戳占41位,数据中心ID占5位,机器ID占5位,自增序列占12位,还有1位是符号位。
这个符号位可以是无符号,这样就可以扩大使用时间或其他地方的bit位数,但在JAVA中无法忽略符号位,因为JAVA中没有unsigned这么个玩意。
首位废弃 | 41位时间戳 | 5位机房码 | 5位机器码 | 12位序列号 |
---|---|---|---|---|
0 | 00000011 00001100 01010000 11001010 00110000 0 | 11111 | 11111 | 00000000 0000 |
#0b11111110101111100001010000100000000100010000000010000000000001
#可以看到上文所述的第一位是标识符,此后是41位的时间戳,紧接着10位的节点标识码,最后12位的递增序列,从后面数12位是:000000000001,再数5位是:00010 这5位就是某个节点的存储标识,但是它目前是二进制,我们再将它转换为十进制
3.组成部位的作用
- 41位时间戳,用来根据时间变化做递增,并且可以和最后一次生成时间做对比,如果处于同一毫秒。该如何处理
- 10位数据中心ID,相同的代码,相同的时间,但数据中心ID不同,就不会生成出相同的UUID。
机器ID,相同的代码,相同的时间,相同数据中心,但机器ID不同,就不会生成出相同的UUID。 - 12位递增序列,如果处于同一毫秒内,递增序列则可以自增,保证ID的不唯一,自增ID最大12字节也就是2^12-1(4095),也就是说,一台机器,可以在同1毫秒内生成4096个ID(为0时也算一个),一秒就可以生成4,096,000个ID。
4.解决了什么问题
首先要知道使用数据库自增ID有什么问题?
- 如果数据库使用AUTO_INCREMENT配合replace into自增ID,那么每当数据插入都会占用自增锁和插入锁
- 尝试爆破就可以大概了解此公司业务量
- 使用redis String进行increment生成有序UUID,redis如果为了高可用,你还要设置redis集群中的服务器对应的自增序列步长。
- UUID库生成字符串唯一ID,没有有序性,查找数据性能较差(在数据库中的索引如果没有有序性,插入时索引就要不停的调平衡,哪怕是B+Tree也扛不住你随机字符串的UUID)。
- 单机情况下有些人用时间戳+几位随机数做ID,分布式肯定凉凉,并且单机情况下也会有几率重复。你无法确定同一时间内是否会出现相同的随机数。
5.使用流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqUdHSMh-1655642945343)(C:\Users\guxingyu\AppData\Roaming\Typora\typora-user-images\image-20220616220611617.png)]
时钟回拨
因为机器的原因会发生时间回拨,我们的雪花算法是强依赖我们的时间的,如果时间发生回拨,有可能会生成重复的ID,在我们上面的nextId中我们用当前时间和上一次的时间进行判断,如果当前时间小于上一次的时间那么肯定是发生了回拨,算法会直接抛出异常
**注:**这里简单说一下时钟回拨的解决思路:
第一种: 发现时钟回拨可以进行上锁等待差值后重试。
第二种: 借用未来时间
发现时钟回拨后利用redis或本地文件映射至内存存储差值,进行增加currentTimeMillis变量的值。如果一段时间内没有申请ID,那么currentTimeMillis就会变得>lastTimeStamp,此时清除差值即可。
第三种:从架构上解决
雪花服务分为两部分,每部分N台机器,如果每部分服务中有⼀台机器发⽣了时间回拨,那么就负载均衡访问另⼀部分的服务。如果另⼀部分服务还是时间回拨,那么才报错。
参考链接:https://blog.csdn.net/slslslyxz/article/details/106330758
6.使用方法
#首先安装库
pip3 install pysnowflake
#安装完成后,就可以在本地命令行启动snowflake服务
snowflake_start_server --worker=1
#生成唯一id
import snowflake.client
print(snowflake.client.get_guid())
#4589032814791368705
#解析成二进制
print(bin(4589032814791368705))
#0b11111110101111100001010000100000000100010000000010000000000001
#可以看到上文所述的第一位是标识符,此后是41位的时间戳,紧接着10位的节点标识码,最后12位的递增序列,从后面数12位是:000000000001,再数5位是:00010 这5位就是某个节点的存储标识,但是它目前是二进制,我们再将它转换为十进制
print(int('00010',2))
7.使用过程中可能出现的问题
原因:,由于每毫秒从0开始计数,如果id生成不频繁,即每毫秒只会生成一个甚至几毫秒才需要生成一个,那么计数器就永远都是0,生成的id用于取模分表的话,就会出现永远都是插入到一个表(或规律的那几个表),无法达到均匀分布在各表的目的,