分布式id要考虑的问题

  • 全局唯一
  • 高可用:确保任何时候都能正确生成id
  • 高性能:id生成响应要快、低延时,否则反倒会成为业务瓶颈
  • 简单易用:在设计和实现上要尽可能的简单,拿来即用
  • 是否需要是有序递增、需要包含日期时间等特殊部分:具体看业务场景

 

常见实现方案

  • uuid
  • 数据库主键自增
  • 号段模式
  • Redis
  • 雪花算法snowflake

 

uuid

标准格式:以-将36个字符分为5段,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),包含

  • 当前日期时间
  • 时钟序列
  • 全局唯一的IEEE机器识别号。如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
     
//根据需要去除-
String uuid = UUID.randomUUID().toString().replaceAll("-", "");

 

优点

  • 本地生成,没有网络通信消耗,id生成性能高,没有高可用风险
  • 简单易用

缺点

  • 数据库主见字段长度越短越好,uuid长度太长,如果作为数据库主键、索引,查询效率低;在InnoDB引擎下,uuid的无序性会导致数据位置频繁变动,不易存储,存储、查询对数据库性能消耗较大,严重影响性能。
  • 不能表示具体的业务含义
  • 无序,不满足递增需求
  • uuid中可能包含了mac地址,有mac地址泄露的风险

uuid一般用于生成文件名。

 

数据库主键自增

优点

  • 实现简单、性能可以接受
  • 有序递增

缺点

  • 数据库常见架构是一主多从+读写分离,生成自增ID是写请求,主库故障时不能生成id,有单点故障风险,可用性低;主库的写性能决定id的生成性能,性能不高。
  • 不同数据库实现方式不同,数据库迁移时需要额外处理。
  • 分库分表时会有麻烦。
     

优化方案:双(多)主模式集群

id字段需要设置初始值、步长。

eg. 主库1的id初始值是1,主库2的id初始值是2,步长都是2,则主库1生成的id是1、3、5、7、9…,主库2生成的id是2、4、6、8、10…
 

优点

  • 提升了可用性、解决了主库单点故障问题
  • 多个主库,提升了生成id的性能

缺点

  • 后续扩容麻烦,增加主库节点时需要修改其他主库的初始值、步长设置

 

号段模式

数据表 id_generator

字段 描述
id 这张表的主键字段
biz_type 业务类型
max_id 已使用的号段区间的最大值
step 步长,即号段中id的数量
version 版本号,用于实现乐观锁,更新此表时使用

 

初始记录示例

1 1 0 2000 0

 

1这种类型的业务申请号段时

-- 先查询此种业务类型已使用的最大id、步长、版本号,此次要申请的号段是(max_id,max_id+step)。
-- 0、2000 =》(0,0+2000】
select max_id, step, version from id_generator where biz_type=1;

-- 再更新此种业务类型对应的记录,version保证并发下的整个申请操作的原子性
update id_generator set max_id=#{max_id+step}, version=version+1 where biz_type=1 and version=#{version};

执行成功则申请到(0,2000】号段,将号段范围、当前使用到的id保存到内存中,后续直接使用申请到的这2000个id,用完再次申请即可。
 

优点

  • 不强依赖于数据库,不会频繁访问数据库,对数据库的压力小

缺点

  • 编码量大

 

借助redis的自增操作

Redis是单线程的,可以用Redis的原子操作 incr、incrby来实现分布式id。

这2个命令,如果key不存在,会先初始化为0,在执行incr操作。

可以用redis来生成每天从0开始的流水号,eg. 订单号=日期+当日自增长号,可以每天在redis中生成一个key,使用incr进行累加。
 

优点

  • 不依赖数据库,灵活方便,且性能优于数据库
  • 数字id,天然有序递增

缺点

  • 需要引入redis,增加了编码量
  • redis是内存数据库,rdb持久化方式可能发生数据丢失,造成id重复
     

优化方案:使用Redis集群获取更高的吞吐量

需要给redis各个节点设置初始值、步长。

 

雪花算法snowflake

snowflake是twitter开源的分布式ID生成算法,一些公司在其基础上进行了改良,先生成一个64位的二进制正整数,然后转换为10进制数。

64位的二进制数结构如下
分布式id_数据库

  • 1bit作为符号位,不用
  • 41bit为毫秒数,可以记录69年
  • 10bit为机器编号,可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID
  • 12bit为毫秒内序列号,最多可以记录4095个

理论上单机每秒内最多可以生成1000*(2^12),也就是400W的ID,一般都能满足业务需求。

以上只是一个划分标准,不一定要这么做,可根据业务场景自行划分。
 

优点

  • 简单高效,生成速度快
  • 时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序递增
  • 灵活度高,可根据业务需求,调整bit位的划分

缺点

  • 依赖机器时钟,在分布式环境上,每个服务器的时钟不可能完全同步,有时会出现不是全局递增的情况,如果服务器时钟回拨,会导致生成的id重复

 

百度开源的 uid-generator

基于snowflake实现,支持自定义bit位。

需要和数据库配合使用。

github地址:https://github.com/baidu/uid-generator

 

美团开源的Leaf

同时支持号段模式和snowflake算法模式,可以切换使用,其中号段模式需要借助数据库,snowflake需要借助zk。

github地址:https://github.com/Meituan-Dianping/Leaf

 

滴滴开源的tinyid

基于号段模式实现,依赖于数据库。

github地址:https://github.com/didi/tinyid