基于Redis实现分布式全局唯一ID生成器的一种可靠方案
为什么需要全局唯一ID
传统的单体架构的时候,我们基本是单库然后业务单表的结构。每个业务表的ID一般我们都是从1增,通过AUTO_INCREMENT=1设置自增起始值,但是在分布式服务架构模式下分库分表的设计,使得多个库或多个表存储相同的业务数据。这种情况根据数据库的自增ID就会产生相同ID的情况,不能保证主键的唯一性。
分布式系统中生成全局id方法由很多,我们选择的时候也比较纠结。每种方式都有各自的使用场景,如果我们熟悉各种方式及优缺点,结合自身的业务,使用的时候才能更好的选择。下面先看一下几种常见的:UUID、数据库预取、雪花算法、Redis自增等等
PS:后续补充下,目前业界已经有比较多成熟的解决方案,比如:百度UidGenerato、美团Leaf等,功能都非常强大,可以直接拿来适用或者借鉴!!
常见的ID生成策略
UUID
UUID (Universally Unique Identifier)
,通用唯一识别码的缩写。UUID是由一组32位数的16进制数字所构成,所以UUID理论上的总数为16^32=2^128
,约等于3.4 x 10^38
。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。优点:本地生成ID,不需要进行远程调用,时延低,性能高。
缺点:
- UUID过长,很多场景不适用,比如用UUID做数据库索引字段。
- 没有排序,无法保证趋势递增
数据库生成
专门建一张表,用来生成id,每台服务器每次提前生成一批订单号放在内存中,每次使用的时候取一个,当库里没有了,再生成一批
优点:
- 简单,能够保证唯一性
缺点:
- 可用性难以保证,强依赖DB,当DB异常时整个系统不可用。虽然配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号
- 不能保证插入时间和id一样完全一致递增,难以满足部分按时间排序需求
雪花算法-Snowflake
Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。
- 第1位占用1bit,其值始终是0,可看做是符号位不使用。
- 第2位开始的41位是时间戳,41-bit位可表示2^41个数,每个数代表毫秒,那么雪花算法可用的时间年限是
(1L<<41)/(1000L360024*365)
=69 年的时间。 - 中间的10-bit位可表示机器数,即2^10 = 1024台机器,但是一般情况下我们不会部署这么台机器。如果我们对IDC(互联网数据中心)有需求,还可以将 10-bit 分 5-bit 给 IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
- 最后12-bit位是自增序列,可表示2^12 = 4096个数。
优点:
- 时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序
- 性能高,在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID
- 可以根据自身业务需求灵活调整bit位划分,满足不同需求
缺点:
- 依赖机器时钟,如果机器时钟回拨,会导致重复ID生成
- 在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不时全局递增的情况
使用redis实现
Redis实现分布式唯一ID主要是通过提供像
INCR
和INCRBY
这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。优点:
- 性能比较高
- 生成的数据是绝对有序的,可以方便业务实现根据id排序
- 可以根据自身业务需求灵活调整bit位划分,满足不同需求
缺点:
- 依赖于redis,需要系统引进redis,增加了系统的复杂性
- 在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不时全局递增的情况
最终采用方案
因为业务上需要id按照插入时间严格递增,并且尽量保持连续,对于性能也需要有一定保证,刚好我们系统也需要Redis,最终确定Redis方案,另外,为了防止Redis宕机或者系统重启出现id重复,每达到一定步长,会异步将当前值记录到MySQL中。下边时id设置及记录表结构:
1 | CREATE TABLE id_sequnce ( |
sequenceDiagram autonumber 服务器->>Redis: 使用INCRBY实现指定步长自增 服务器->>MySQL: 每间隔write_step,异步更新current_id Redis->>MySQL: Redis宕机或服务重启,获取current_id+write_step,防止id重复
Redis使用Cluster集群方式搭建
MySQL部署使用MHA部署