分布式ID方案有哪些以及各自的优劣势
分布式ID生成器解决方案-CSDN
分布式ID解决方案-头条
分布式系统唯一ID生成方案汇总-代码
背景
在分布式系统中,经常需要对大量的数据、消息、http请求等进行唯一标识,例如:在分布式系统之间http请求需要唯一标识,调用链路分析的时候需要使用这个唯一标识。这个时候数据库自增主键已经不能满足需求,需要一个能够生成全局唯一ID的系统,这个系统需要满足以下需求:
全局唯一:不能出现重复ID。
高可用:ID生成系统是基础系统,被许多关键系统调用,一旦宕机,会造成严重影响。
经典方案介绍
1. UUID
UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符,UUID是16字节128位长的数字,通常以36字节的字符串表示,比如:3F2504E0-4F89-11D3-9A0C-0305E82C3301。
UUID经由一定的算法机器生成,为了保证UUID的唯一性,规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成UUID的算法。UUID的复杂特性在保证了其唯一性的同时,意味着只能由计算机生成。
优点:
本地生成ID,不需要进行远程调用,时延低,性能高。
缺点:
UUID过长,16字节128位,通常以36长度的字符串表示,很多场景不适用,比如用UUID做数据库索引字段。
没有排序,无法保证趋势递增。
2. Flicker方案(自增长机制)
这个方案是由Flickr团队提出,设计单独的库表,单独提供产生全局ID的服务,主要思路采用了MySQL自增长ID的机制(auto_increment + replace into)
CREATE TABLE Tickets64 (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
)ENGINE=MyISAM;
#每次业务使用下列SQL读写MySQL得到ID号
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
replace into 跟 insert 功能类似,不同点在于:replace into 首先尝试插入数据到表中,如果发现表中已经有此行数据(根据主键或者唯-索引判断)则先删除此行数据,然后插入新的数据, 否则直接插入新数据。
为了避免单点故障,最少需要两个数据库实例,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
Server1:
auto-increment-increment = 2
auto-increment-offset = 1
Server2:
auto-increment-increment = 2
auto-increment-offset = 2
优点:
充分借助数据库的自增ID机制,可靠性高,生成有序的ID。
缺点:
ID生成性能依赖单台数据库读写性能。
依赖数据库,当数据库异常时整个系统不可用。
对于依赖MySql性能问题,可用如下方案解决:
在分布式环境中我们可以部署多台,每台设置不同的初始值,并且步长为机器台数,比如部署N台,每台的初始值就为0,1,2,3…N-1,步长为N。
以上方案虽然解决了性能问题,但是也存在很大的局限性:
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
数据库压力大:每次获取一个ID都必须读写一次数据库。
3. 阿里-TDDL序列生成方式(取出一定数量放内存中)
TDDL是阿里的分库分表中间件,它里面包含了全局数据库ID的生成方式,主要思路:
使用数据库同步ID信息。
每次批量取一定数量的可用ID在内存中,使用完后,再请求数据库重新获取下一批可用ID,每次获取的可用ID数量由步长控制,实际业务中可根据使用速度进行配置。
每个业务可以给自己的序列起个唯一的名字,隔离各个业务系统的ID。
数据表设计:
seqName varchar(100) 序列名称,主键
cur_value bigint(20) 当前值
step int 步长,根据实际情况设置
优点:
- 相比flicker方案,大大降低数据库写压力,数据库不再是性能瓶颈。
- 相比flicker方案,生成ID性能大幅度提高,因为获取一个可用号段后在内存中直接分配,相对于每次读取数据库性能提高了几个量级。
- 不同业务不同的ID需求可以用seqName字段区分,每个seqName的ID获取相互隔离,互不影响。
缺点:
强依赖数据库,当数据库异常时整个系统不可用。
4. twitter-snowflake方案
snowflake是twitter开源的分布式ID生成系统。 Twitter每秒有数十万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同。
这种方案生成一个64bit的数字,64bit被划分成多个段,分别表示时间戳、机器编码、序号。
41位的时间序列(精确到毫秒,41位的长度可以使用69年)。
10位的机器标识(10位的长度最多支持部署1024个节点)。
12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)。
优点:
时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序。
性能高,每秒可生成几百万ID。
可以根据自身业务需求灵活调整bit位划分,满足不同需求。
缺点:
强依赖时钟,如果主机时间回拨,则会造成重复ID,会产生
ID虽然有序,但是不连续
在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。
5、Redis生成ID
当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。
可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。
另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
2)需要编码和配置的工作量比较大。
Redis Incr 命令将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
实例
redis> SET page_view 20
OK
redis> INCR page_view
(integer) 21
redis> GET page_view # 数字值在 Redis 中以字符串的形式保存
"21"
Redis Incrby 命令将 key 中储存的数字加上指定的增量值。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
实例
# key 存在且是数字值
redis> SET rank 50
OK
redis> INCRBY rank 20
(integer) 70
redis> GET rank
"70"
...