分布式 ID 是指,在分布式环境下可用于对数据进行标识且易存储的全局唯一的 ID 标识。
对于单体系统来说,主键ID可能会常用主键自动的方式进行设置,这种ID生成方法在单体项目是可行的。
对于分布式系统,分库分表之后,就不适应了,比如订单表数据量太大了,分成了多个库,如果还采用数据库主键自增的方式,就会出现在不同库或表中id一致的情况。
分布式 ID 是我们在非常多的场景下用到的组件,对其要求比较高,其一般需要满足以下条件:
下面列出的这几种方案都是生成 ID 的常用方法:
UUID 是通用唯一识别码的缩写(Universally Unique Identifier)。UUID 一般是由一组 32 位数的 16 进制数字所构成,以连字号分为五段,形式为8-4-4-4-12的36个字符,常包含时间戳和 MAC 地址信息这些元素,标准的 UUID 格式为:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器:设置步长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 号段范围,一般表内容如下:
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};
Redis分布式ID实现主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令,由于Redis单线程的特点,可以保证ID的唯一性和有序性。
在 Zookeeper 中主要通过节点数据版本号来生成序列号,可以生成 32 位和 64 位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。在 Zookeeper 中本身就是支持集群模式,所以能保证高可用性,且生成的 ID 为趋势递增且有序,不过在实际使用中很少用 Zookeeper 来充当 ID 生成器,因为 Zookeeper 中存在强一致性,在高并发场景下其性能可能很难满足需求。
不过由于使用 Zookeeper 节点的版本号来充当 ID 号是比较繁琐,需要创建节点获取生成的 ID,然后去掉节点命令前缀,只截取数字部分,最后还要异步执行删除节点(启动新的线程执行删除节点操作,防止占用生成ID线程执行的实际)。过程比较耗时且繁琐,所以,在操作 Zookeeper 时经经常不会采用该方案,常使用 Curator 客户端提供的基于乐观锁的计数器来自增实现 ID 生成,这个过程和数据库自增生成 ID 类似。
Snowflake,雪花算法是由 Twitter 开源的分布式ID生成算法,以划分命名空间的方式将
64-bit位分割成多个部分,每个部分代表不同的含义,64位,在java中Long类型是64位的,所以java程序中一般使用Long类型存储
其结构组成:
百度的 UidGenerator 是百度开源基于Java语言实现的唯一ID生成器,是在雪花算法 snowflake 的基础上做了一些改进。
详细介绍请看官网。
Leaf 提供两种生成的ID的方式:号段模式(Leaf-segment)和 snowflake 模式(Leaf-snowflake)。你可以同时开启两种方式,也可以指定开启某种方式,默认两种方式为关闭状态。
详细介绍请看官网。
Tinyid 是用Java开发的一款分布式id生成系统,基于数据库号段算法实现。Tinyid 扩展了 leaf-segment 算法,支持了多数据库和 tinyid-client。
详细介绍请看官网。
参考
SmileNicky的博客
myf008的博客