什么是分布式 ID

分布式 ID 是指,在分布式环境下可用于对数据进行标识且易存储的全局唯一的 ID 标识。

为什么需要分布式 ID

对于单体系统来说,主键ID可能会常用主键自动的方式进行设置,这种ID生成方法在单体项目是可行的。

对于分布式系统,分库分表之后,就不适应了,比如订单表数据量太大了,分成了多个库,如果还采用数据库主键自增的方式,就会出现在不同库或表中id一致的情况。

分布式 ID 需要满足的条件

分布式 ID 是我们在非常多的场景下用到的组件,对其要求比较高,其一般需要满足以下条件:

  • 全局唯一性:ID是作为唯一的标识,不能出现重复
  • 高性能:高可用低延时,ID 生成速度要快,否则反倒会成为业务瓶颈
  • 高可用:尽量保证服务的可用性,多实例化,避免因一个实例挂掉影响整个业务应用的运行
  • 容易接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单,避免增加开发人员的使用成本
  • 趋势递增:互联网比较喜欢MySQL数据库,而MySQL数据库默认使用InnoDB存储引擎,其使用的是聚集索引,使用有序的主键ID有利于保证写入的效率
  • 单调递增:保证下一个ID大于上一个ID,这种情况可以保证事务版本号,排序等特殊需求实现
  • 信息安全:前面说了ID要递增,但是最好不要连续,如果ID是连续的,容易被恶意爬取数据,指定一系列连续的,所以ID递增但是不规则是最好的

常用分布式 ID 生成方案

下面列出的这几种方案都是生成 ID 的常用方法:

  • 使用 UUID 生成 ID
  • 使用数据库自增生成 ID
  • 使用数据库号段模式生成 ID
  • 使用 Redis 实现生成 ID
  • 使用 Zookeeper 生成 ID
  • 根据雪花算法(Snowflake)算法生成 ID
  • 百度Uidgenerator
  • 美团Leaf
  • 滴滴TinyID

使用 UUID 生成 ID

UUID 是通用唯一识别码的缩写(Universally Unique Identifier)。UUID 一般是由一组 32 位数的 16 进制数字所构成,以连字号分为五段,形式为8-4-4-4-12的36个字符,常包含时间戳和 MAC 地址信息这些元素,标准的 UUID 格式为:

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

优点

  • 高性能
  • 实现简单
  • 不需要第数据库等第三方组件依赖

缺点

  • 并不是趋势递增,不方便排序
  • 生成的 ID 只能用字符串类型存储,占用空间大
  • 生成的串没有规律,出问题时不易根据 ID 进行排查

使用数据库自增生成 ID

在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器:设置步长step为2,Server1的初始值为1(1,3,5,7,9,11…)、Server2的初始值为2(2,4,6,8,10…)。

这种方案看起来是可行的,但是如果要扩容,步长step等要重新设置,假如只有一台机器,步长就是1,比如1,2,3,4,5,6,这时候如果要进行扩容,就要重新设置,机器2可以挑一个偶数的数字,这个数字在扩容时间内,数据库自增要达不到这个数的,然后步长就是2,机器1要重新设置step为2,然后还是以一个奇数开始进行自增。这个过程看起来不是很杂,但是,如果机器很多的话,那就要花很多时间去维护重新设置。

优点

  • 实现简单
  • 趋势递增

缺点

  • ID没有了单调递增的特性,只能趋势递增,有些业务场景可能不符合
  • 数据库压力还是比较大,每次获取ID都需要读取数据库,只能通过多台机器提高稳定性和性能
  • 水平扩展比较麻烦,需要手动调整集群数据库中的初始值与步长

使用数据库号段模式生成 ID

这种模式也是现在生成分布式ID的一种方法,在使用号码模式时,我们通常会先建立一张表用于记录上述的 ID 号段范围,一般表内容如下:

CREATE TABLE id_generator (
  id int(10) NOT NULL AUTO_INCREMENT,
  max_id bigint(20) NOT NULL COMMENT '当前最大id',
  step int(20) NOT NULL COMMENT '号段的布长',
  biz_type    int(20) NOT NULL COMMENT '业务类型',
  version int(20) NOT NULL COMMENT '版本号',
  PRIMARY KEY (`id`)
)

每次从数据库中获取号段 ID 的范围时,都会执行更新语句,其中计算新号段范围最大值 max_id 的公式是 max_id + step 组成,所以 SQL 中设置 max_id = max_id+step 来执行更新语句,更新数据库中这个范围最大值 max_id,然后再通过查询语句查询更新后 ID 最大值,再根据最大值 max_id 与步长 step 计算出待生成的 ID 的范围,其中操作的 SQL 如下:

update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = #{biz_type};


SELECT `max_id`, `step`, `version` FROM `myid` WHERE `biz_type` = #{biz_type};

优点

  • 使用缓存机制,容灾性高,即使数据库不可用还能撑一段时间。
  • 可以自定义每次扩展的大小,控制 ID 生成速度;
  • 可以设置生成 ID 的初始范围,方便业务从原有的 ID 方式上迁移过来。
  • 有比较成熟的方案,像百度Uidgenerator,美团Leaf

缺点

  • 依赖于数据库实现,数据库宕机会造成整个系统不可用。
  • ID 号码不够随机,可能够泄露发号数量的信息,不太安全。
     

使用 Redis 实现生成 ID

Redis分布式ID实现主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令,由于Redis单线程的特点,可以保证ID的唯一性和有序性。

优点

  • 实现简单;
  • 有序递增,方便排序;

缺点

  • 强依赖于 Redis,可能存在单点问题;
  • 如果 Redis 超时,可能会对业务造成影响;
  • 占用宽带,而且需要考虑网络延时等问题带来地性能冲击。

使用 Zookeeper 生成 ID

在 Zookeeper 中主要通过节点数据版本号来生成序列号,可以生成 32 位和 64 位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。在 Zookeeper 中本身就是支持集群模式,所以能保证高可用性,且生成的 ID 为趋势递增且有序,不过在实际使用中很少用 Zookeeper 来充当 ID 生成器,因为 Zookeeper 中存在强一致性,在高并发场景下其性能可能很难满足需求。

分布式ID生成方案总结_自增

不过由于使用 Zookeeper 节点的版本号来充当 ID 号是比较繁琐,需要创建节点获取生成的 ID,然后去掉节点命令前缀,只截取数字部分,最后还要异步执行删除节点(启动新的线程执行删除节点操作,防止占用生成ID线程执行的实际)。过程比较耗时且繁琐,所以,在操作 Zookeeper 时经经常不会采用该方案,常使用 Curator 客户端提供的基于乐观锁的计数器来自增实现 ID 生成,这个过程和数据库自增生成 ID 类似。

优点

  • 高可用
  • 趋势递增

缺点

  • 性能差
  • 定期删除之前生成的节点,比较繁琐

根据雪花算法(Snowflake)算法生成 ID

Snowflake,雪花算法是由 Twitter 开源的分布式ID生成算法,以划分命名空间的方式将
64-bit位分割成多个部分,每个部分代表不同的含义,64位,在java中Long类型是64位的,所以java程序中一般使用Long类型存储
 

分布式ID生成方案总结_ID_02

其结构组成:

  • 第一部分:第一位占用1bit,始终是0,是一个符号位,不使用
  • 第二部分:第2位开始的41位是时间戳。41-bit位可表示241个数,每个数代表毫秒,那么雪花算法可用的时间年限是(241)/(1000606024365)=69 年的时间
  • 第三部分:10-bit位可表示机器数,即2^10 = 1024台机器。通常不会部署这么多台机器
  • 第四部分:12-bit位是自增序列,可表示2^12 = 4096个数。觉得一毫秒个数不够用也可以调大点

优点:

  • 高性能
  • 趋势递增
  • 可以灵活调整结构
  • 不需要第数据库等第三方组件依赖

缺点:

  • 强依赖时钟,可能发生时钟回拨导致生成的 ID 重复

百度 Uidgenerator

百度的 UidGenerator 是百度开源基于Java语言实现的唯一ID生成器,是在雪花算法 snowflake 的基础上做了一些改进。

详细介绍请看官网

美团 Leaf

Leaf 提供两种生成的ID的方式:号段模式(Leaf-segment)和 snowflake 模式(Leaf-snowflake)。你可以同时开启两种方式,也可以指定开启某种方式,默认两种方式为关闭状态。

详细介绍请看官网

滴滴 TinyID

Tinyid 是用Java开发的一款分布式id生成系统,基于数据库号段算法实现。Tinyid 扩展了 leaf-segment 算法,支持了多数据库和 tinyid-client。

详细介绍请看官网

参考

SmileNicky的博客

myf008的博客