雪花算法
Snowflake是一种分布式唯一ID生成算法,旨在解决分布式系统中唯一ID生成的需求。其ID结构如下:
-
第一位:符号位,始终为0,因为Snowflake生成的是正数。
-
41位时间戳:记录自定义的开始时间戳以来的毫秒数差值,支持约69年的时间戳。
-
10位数据中心ID和工作机器ID:分别用来标识数据中心和机器,支持最多1024个数据中心和每个数据中心最多1024台机器。
-
12位序列号:在同一毫秒内(同一台机器,同一时间戳)自增的计数器,支持每台机器每毫秒最多生成4096个唯一ID。
Snowflake算法的优点包括:
-
高效性:每秒能够生成大量唯一ID,测试显示可以达到约26万个ID。
-
唯一性:整个分布式系统内不会产生ID碰撞,由数据中心ID和机器ID作区分。
-
有序性:生成的ID按照时间自增排序,可以方便地根据ID的时间戳进行排序查询。
-
简单:算法相对简单,易于实现和部署。
Snowflake的核心在于使用时间戳、数据中心ID、机器ID和序列号的组合来保证生成的ID是唯一且有序的,适合于需要高性能、高可用且唯一标识的分布式系统场景。
Twitter_Snowflake SnowFlake的结构如下(每部分用-分开): 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
-
1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0 41位时间截(毫秒级),注意,
-
41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。
-
41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
-
10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
-
12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号 加起来刚好64位,为一个Long型。 SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
雪花算法时钟回拨问题
雪花算法强依赖时间戳,所以就会产生时钟回拨问题。也就是Linux服务器(系统)时间和真实时间不一致,可能会存在几秒的差异。就需要修改Linux时间,简单来说就是会出现取号问题。
通常可以用以下方式解决:
-
异常处理,抛出异常,停止ID生成,但是这样就会暂停ID生成服务,也会影响系统的可用性。
-
等待时间恢复,可以让线程睡眠等待,知道时间恢复。
-
生成备用ID,比如随机数或者其他方式,但这种就需要增加额外的机制
-
多时钟法,重新定个时钟,(可以拆分10位机器码,前几位作为不同时钟)
常用的分布式 ID 设计方案
首先,分布式全局 ID 的解决方案有很多,比如:
-
使用 Mysql 的全局表
-
使用 Zookeeper 的有序节点
-
使用 MongoDB 的 objectid
-
redis 的自增 id
-
UUID 等等
这些方案只是解决基础的 id 唯一性问题,在实际生产环境中,需要构建一个全局唯一 id
还需要考虑更多的因素:
-
有序性, 有序的 ID 能够更好地确认数据的位置,
以及 B+数据的存储结构中,范围查询的效率更高,并且可以提升 B+树数据维护的效率。
-
安全性,避免恶意爬取数据造成数据泄露
-
可用性,ID 生成系统的可用性要求非常高,一旦出现故障就会造成业务不可用的问题
-
性能,全局 id 生成系统需要满足整个公司的业务需求,涉及亿级别的调用,对性能要求较高
因此,如果我们选择数据库的全局表,你每获取一次 id 就需要更新数据库,性能上限比较明显,
而且基于数据库构建高扩展和高性能的解决方案难度很大。
所以,目前市面上主流的解决方案是基于 Twitter 早期开源的 Snowflake 雪花算法(图片)。
它是由 64 位长度组成的全局 id 生成算法,通过对 64 位进行区间划分来表述不同含义实现唯一性
标识符一般不用,41位是系统毫秒级时间戳,10位工作机器ID,12位序列表示同毫秒内生成不同ID的能力。
它的好处是:
-
算法实现简单
-
不存在太多外部依赖
-
可以生成有意义的有序编号
-
基于位运算,性能也很好,Twitter 测试的生成ID峰值是 10 万个每秒。
另外,美团公司开源了一个全局唯一 id 生成系统 leaf,它里面也用到了雪花算法去构建全局唯一 id
并且在高性能和高可用方面,做了很多的优化,为美团内部业务提供了每天上亿次的调用。
雪花算法原理
雪花算法是一种生成分布式全局唯一 ID 的算法,它会得到一个 64 位长度的 long 类型数据。
其中这 64 位的数据,由 4 个部分组成
-
第一个 bit 位是符号位,因为 id 不会是负数,所以它一般是 0
-
接着用 41 个 bit 位来表示系统级毫秒单位的时间戳
-
再用 10 个 bit 位来表示工作机器 id,保证不同服务器生成ID唯一性
-
最后 12 个 bit 位表示递增的序列号来记录同毫秒内产生的不同 id
为什么MySQL分布式架构不能使用自增ID或者UUID做主键
为什么不能使用自增ID
在分布式系统下,使用自增ID,就会出现如下问题:
比如第一种情况,在不同库存放固定范围,1-10000,10000-20000,20000-30000,就只会先在第一个库存放,意味着一个时间段,生成的ID实际集中在一个库上,就起不到负载均衡,分担数据库压力的问题。第二种情况,将使用过的ID同步到其他库,这就需要加锁,非常损耗性能。第三种情况,每个库都自己维护自己的ID,这样查询时,ID就会重复不唯一。
使用UUID,全局唯一,当然可以当做主键,但是由于B+树是有序性,每次插入就会导致树的结构变化,为了维护树的有序性,就会导致数据量的迁移,也非常损耗性能。
所以比较雪花算法,具有全局唯一,实现简单,趋势递增,性能高等优势,注意雪花算法不是单调递增。
雪花算法是由一个 64 位长度的 long 类型数据组成。
其中这 64 位的数据,由 4 个部分组成
-
第一个 bit 位是符号位,因为 id 不会是负数,所以它一般是 0
-
接着用 41 个 bit 位来表示系统级毫秒单位的时间戳,可以表示69.7年左右,一般用时间差表示。
-
再用 10 个 bit 位来表示工作机器 id,保证不同服务器生成ID唯一性
-
最后 12 个 bit 位表示递增的序列号来记录同毫秒内产生的不同 id
雪花算法强依赖时间戳,所以就会产生时钟回拨问题。也就是Linux服务器(系统)时间和真实时间不一致,可能会存在几秒的差异。就需要修改Linux时间,简单来说就是会出现取号问题。
通常可以用以下方式解决:
-
异常处理,抛出异常,停止ID生成,但是这样就会暂停ID生成服务,也会影响系统的可用性。
-
等待时间恢复,可以让线程睡眠等待,知道时间恢复。
-
生成备用ID,比如随机数或者其他方式,但这种就需要增加额外的机制
-
多时钟法,重新定个时钟,(可以拆分10位机器码,前几位作为不同时钟)