需求背景
这个记录标识往往就是数据库中的主键,数据库上会建立聚集索引(cluster index),即在物理存储上以这个字段排序。
这个记录标识上的查询,往往又有分页或者排序的业务需求,例如:
-
拉取最新的一页消息
select message-id/ order by time/ limit 100
-
拉取最新的一页订单
select order-id/ order by time/ limit 100
-
拉取最新的一页帖子
select tiezi-id/ order by time/ limit 100
所以往往要有一个time字段,并且在time字段上建立普通索引(non-cluster index)。
普通索引存储的是实际记录的指针,其访问效率会比聚集索引慢,如果记录标识在生成时能够基本按照时间有序,则可以省去这个time字段的索引查询: select message-id/ (order by message-id)/limit 100
强调,能这么做的前提是,message-id的生成基本是趋势时间递增的。
这就引出了记录标识生成(也就是上文提到的三个XXX-id)的两大核心需求:
-
全局唯一
-
趋势有序
这也是本文要讨论的核心问题:如何高效生成趋势有序的全局唯一ID。
一 SnowFlake 算法
优势:
1 保证分布式场景下生成的ID是唯一的
2 生成的全局ID整体上是呈自增趋势的,也就是说整体是有序的
3 高性能,能快速产生ID
4 只占64bit位空间,可以根据业务需求扩展在前缀或后缀拼接业务标志位转换为字符串
缺点:
1 由于“没有一个全局时钟”,每台服务器分配的ID是绝对递增的,但从全局看,生成的ID只是趋势递增的(有些服务器的时间早,有些服务器的时间晚)
1 1位,不用。二进制中,最高位为1的都是负数,但是我们生成的ID一般都使用整数,所以这个最高位固定为0
2 41位,用来纪录时间戳(毫秒),41位可以表示2的41次方-1个毫秒的值,转化成单位年则是69年
3 10位 用来纪录工作机器ID。可以部署在2的10次方 1024个节点。包括5位的数据中心ID,和5位的机器ID
4 12位 序列号,用来纪录同毫秒内产生的不同id.12位bit 可以表示的最大正整数是2的12次方减1 是4095 来表示同一个机器同一个时间戳毫秒内产生4096个序号
核心在于分配64的位数 ,举例
假设某公司ID生成器服务的需求如下:
1 单机高峰并发量小于1W,预计未来5年单机高峰小于10w
2 有两个机房,预计未来5年机房数量小于4个
3 每个机房机器小于100台
4 目前有5个业务线有ID生成需求,未来业务线小于10个
分析过程如下,
1 高位取 2018年9月22日(系统ID生成器在这个时间之后上线)到现在的毫秒数,假设系统要运行10年,需要10*365*24*3600*1000等于 320 的10的9次方,2的39次方大概刚好大于这个数,则预留39位给毫秒数
2 每秒的单机高峰并发小于10W,即平均每毫秒的单机并发量小于100,差不多预留7个bit给每秒内序列号
3 5年内机房数小雨4个,预留2个bit
4 每个机房小雨100个机器,留7个bit给每个机房的服务器标识
5 业务线小于10个,预留四个bit给业务标识
这样设计的64bit标识,可以保证:
-
每个业务线、每个机房、每个机器生成的ID都是不同的
-
同一个机器,每个毫秒内生成的ID都是不同的
-
同一个机器,同一个毫秒内,以序列号区区分保证生成的ID是不同的
-
将毫秒数放在最高位,保证生成的ID是趋势递增的
二 美团方案
三 UUID方法
四 数据库生成全局ID方案
五