注意
想法及时记录,实现可以待做。
简介
在复杂的分布式系统中,有时候需要对数据ID做唯一标识,这种情况下,能有一个较好的ID生成器是必不可少的。那么满足这样的ID生成器要求有哪些呢?
-
全局唯一:不能出现重复,这是最重要的保证
-
趋势递增:ID最好是趋势递增的,不一定是严格递增,这样对于存入数据库有帮助
作为生成ID的俯卧撑,本身也要高可用,提供服务的接口延迟较低,性能有保障。
常见方法介绍
基于UUID
550e8400-e29b-41d4-a716-446655440000
如上图所示,UUID共有36个字符,除去4个连字符,剩下有32个16进制的数字,组成方式为 8-4-4-4-12。
优点:
- 性能非常高,本地生成,没有网络消耗
缺点:
- 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用,作为数据库自增ID就不适合。
- 安全风险:基于MAC地址生成UUID可能会造成MAC地址泄露,有安全风险。
基于Twitter Snowflake
雪花算法是Twitter推出来的一种ID生成的算法,它使用64位bit作为全局的ID,这个ID的构成有几部分组成:
1bit 41bit 10bit 12bit
|_____|_______________________________________________|____________|_____________|
| 0 |0001100 10100010 10111110 10001001 01011100 00 |10001 11001 |0000 10001000|
64位的bit中,第一位未使用,41位作为毫秒时间,10位作为机器使用,12位是生成的序列号。
41bit位的时间长度,(1«41)意味着可以使用约69年。
10bit位的机器使用,也就可以标识2^10个机器,如果有机房,可以分配bit给机房使用,这里可以调整。
12bit为的序列号,意味着每毫秒最大可以同时生成(2^12 -1)个不同的ID,这是在满足以上的bit上的最大数量。
算法的实现有很多现成的方案,在此不列举,可以自行搜索。雪花算法只在内存计算,不需要依赖数据库,也不会存在生成ID过程中的网络消耗,性能极高,而且每秒可以产生百万级(1000*(2^12-1))的ID。
而且即使服务重启,也不会出现重复的ID,因为ID与时间相关。
优点:
- 实现简单,不需要依赖第三方组件
- 高性能,高可用
- ID全局唯一,趋势自增
缺点:
- 严重依赖系统时钟,如果出现时间回调,就会生成重复的ID或者服务不可用。
基于Mongo的objectID
从组成桑来看,ObjectID有12个字节,分别是:
- 4个字节的时间戳
- 5个字节的随机值
- 3个字节的计数器
看起来与雪花算法很类似的方式,不过这个有96bit的长度,比64还要长不少。
优点:
- 实现简单
缺点:
- 也存在时间回拨的问题
基于数据库ID
基于单实例MySQL
基于数据库自增ID为主要依据,比如MySQL的数据库实例
CREATE TABLE test_auto_increment (
`id` bigint(20) unsigned NOT NULL auto_increment,
`value` char(10) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into test_auto_increment(value) VALUES ('2021');
SELECT LAST_INSERT_ID();
根据主键ID自增策略,会一直产生递增的ID,但是如果数据量极大,这种方式会造成表的数据量过大,那么可以通过唯一索引的方式,控制表的数量,ID还继续自增。
CREATE TABLE test_auto_increment (
`id` bigint(20) unsigned NOT NULL auto_increment,
`value` char(10) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY `uk_value` (`value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
replace into test_auto_increment(value) VALUES ('2021');
SELECT LAST_INSERT_ID();
可以看到,这次见表,使用了唯一索引,通过replace into
一直更新值,也会造成实际的主键ID自增,从而达到数据量可以控制,ID继续自增的目的。
那么这样是不是就可以了呢?对于请求不频繁的场景来说,没得问题,但是如果请求非常大,那么MySQL自身就成为了性能瓶颈。
优点:
- 实现简单,ID严格递增
缺点:
- MySQL单点故障风险,也不能支持并发过大的场景
基于MySQL集群模式
既然单个实例的MySQL存在故障不可用的情况,那么集群的模式可用性会更高,比如建立主-主模式的集群,为了保证ID全局唯一,要做自增策略调整
第一个MySQL设置
set @@auto_increment_offset = 1; -- Starting value
set @@auto_increment_increment = 2; -- step
第二个MySQL设置
set @@auto_increment_offset = 2; -- Starting value
set @@auto_increment_increment = 2; -- step
这样调整后,两个MySQL分别对外提供的ID是:
1、3、5、7、9
2、4、6、8、10
如果觉得两个示例的集群还不够,不妨在加,然后调整对应的示例的起始值和步长即可,保证不会存在ID重复,并且是趋势递增。
优点:
- 高可用
缺点:
- 想要扩展,有点麻烦,并且对于每个实例来说,在大并发场景依旧会存在性能瓶颈。
基于Redis的自增ID
MySQL的实现方式,不管是单个实例,还是集群模式,获取ID的量大了,始终会存在性能瓶颈,这也是为啥很多系统会引入缓存的原因。说到缓存,Redis的性能无疑是优秀的。
而且,对于保证全局ID唯一,Redis是单线程接收请求的,可以通过自增命令一直生成自增ID,不会出现重复的问题。
127.0.0.1:6379> set seq_id 1 //Initialize auto increment ID to 1
OK
127.0.0.1:6379> incr seq_id //Increases by 1 and returns the incremented value
(integer) 2
参考
- Leaf——美团点评分布式ID生成系统
- Tinyid原理介绍
- 9 kinds of distributed ID generation methods, there is always one for you
- 发号器设计漫谈
- 分布式唯一id:snowflake算法思考
- 理解分布式id生成算法SnowFlake