几乎所有的业务系统,都有生成一个唯一记录标识的需求,例如:消息ID,订单ID,帖子ID...
这个ID,在数据库中往往用作主键,且有排序与分页的查询需求。这也是分布式ID生成算法的两大核心需求:
- 全局唯一;
- 趋势递增;
如何高效生成趋势有序的全局唯一ID,是每一个工程师都会遇到的问题。
方法一:数据库auto-inc-id法
借助数据库的auto_increment来生成全局唯一递增ID。
优点:
- 简单,使用数据库已有的功能;
- 能够保证唯一性;
- 能够保证递增性;
- 步长固定;
不足:
- 可用性难以保证,需要依赖数据库的高可用;
- 扩展性差,性能有上限,数据库主库的写性能决定ID的生成性能上限;
改进方法:
- 冗余主库,避免写入单点;
- 数据水平切分,保证各主库生成的ID不重复;
改进后,数据库的写压力依然很大,每次生成ID都要访问数据库。为了解决这个问题,引出了第二个常见的方案。
方法二:批量ID生成服务
数据库写压力大,是因为每次生成ID都访问了数据库,可以使用批量的方式降低数据库写压力。
ID生成服务假设每次批量拉取6个ID,服务访问数据库,将当前ID的最大值修改为5,这样应用访问ID生成服务索要ID,ID生成服务不需要每次访问数据库,就能依次派发0,1,2,3,4,5这些ID了。
当ID发完后,再将ID的最大值修改为11,就能再次派发6,7,8,9,10,11这些ID了,于是数据库的压力就降低到原来的1/6。
优点:
- 保证了ID生成的绝对递增有序;
- 大大的降低了数据库的压力,ID生成可以做到每秒生成几万几十万个;
同时,服务也可以做集群化,只是稍微要注意数据一致性问题,具体CAS优化方案在《巧用CAS实现分布式ID生成器!》中有详细介绍,不再展开。
方法三:uuid/guid法
不管是通过数据库,还是通过服务来生成ID,业务方都需要进行一次远程调用,比较耗时。有没有一种本地生成ID的方法,即高性能,又时延低呢?
uuid是一种常见的方案:
优点:
- 本地生成ID,不需要进行远程调用,时延低;
- 扩展性好,基本可以认为没有性能上限;
不足:
- 无法保证趋势递增
- uuid过长,往往用字符串表示,作为主键建立索引查询效率低,常见优化方案为“转化为两个uint64整数存储”。
方法四:取当前毫秒数
uuid是一个本地算法,生成性能高,但无法保证趋势递增,且作为字符串ID检索效率低,有没有一种能保证递增的本地算法呢?
取当前毫秒数是一种常见方案:
优点:
- 本地生成ID,不需要进行远程调用,时延低;
- 生成的ID趋势递增;
- 生成的ID是整数,建立索引后查询效率高;
缺点:如果并发量超过1000,会生成重复的ID。
当然,使用微秒可以降低冲突概率,但每秒最多只能生成1000000个ID,再多的话就一定会冲突了,所以使用微秒并不从根本上解决问题。
方法五:类snowflake算法
snowflake是twitter开源的分布式ID生成算法,其核心思想为,一个long型的ID:
- 41bit作为毫秒数;
- 10bit作为机器(服务)编号;
- 12bit作为毫秒内序列号;
算法单机每秒内理论上最多可以生成1000*(2^12),也就是400W的ID,1024台机器(服务)每秒能生活40Y的ID,完全能满足业务的需求。
借鉴snowflake的思想,结合公司的业务逻辑和并发量,可以实现自己的分布式ID生成算法。
举例,假设某公司ID生成的需求如下:
- 单机高峰并发量小于1W,预计未来10年单机高峰并发量小于10W;
- 有2个机房,预计未来10年机房数量小于4个;
- 每个机房机器数小于100台;
- 目前有5个业务线有ID生成需求,预计未来业务线数量小于10个;
- …
我们应该怎么来设计公司独特的ID生成算法呢?
其一,毫秒位数考虑。
假设系统至少运行10年,那至少需要10年*365天*24小时*3600秒*1000毫秒=320*10^9,差不多预留39bit给毫秒数。
其二,1毫秒内序列号考虑。
每秒的单机高峰并发量小于10W,即平均每毫秒的单机高峰并发量小于100,差不多预留7bit给每毫秒内序列号。
其三,机房数少于4个,预留2bit给机房标识。
其四,每个机房机器小于100台,预留7bit给每个机房内的服务器标识。
其五,业务线小于10个,预留4bit给业务线标识。
这样设计的64bit标识,可以保证:
- 每个业务线、每个机房、每个机器生成的ID都是不同的;
- 同一个机器,每个毫秒内生成的ID都是不同的;
- 同一个机器,同一个毫秒内,以序列号区区分保证生成的ID是不同的;
- 将毫秒数放在最高位,保证生成的ID是趋势递增的;
以上,希望大家有收获。
知其然,知其所以然。思路比结论更重要。
扩展阅读:https://github.com/twitter-archive/snowflake