分布式ID的方案介绍

分布式

Posted by qin4zhang on May 6, 2021

注意

想法及时记录,实现可以待做。

简介

在复杂的分布式系统中,有时候需要对数据ID做唯一标识,这种情况下,能有一个较好的ID生成器是必不可少的。那么满足这样的ID生成器要求有哪些呢?

  1. 全局唯一:不能出现重复,这是最重要的保证

  2. 趋势递增:ID最好是趋势递增的,不一定是严格递增,这样对于存入数据库有帮助

作为生成ID的俯卧撑,本身也要高可用,提供服务的接口延迟较低,性能有保障。

常见方法介绍

基于UUID

550e8400-e29b-41d4-a716-446655440000

如上图所示,UUID共有36个字符,除去4个连字符,剩下有32个16进制的数字,组成方式为 8-4-4-4-12。

维基百科UUID

优点:

  • 性能非常高,本地生成,没有网络消耗

缺点:

  • 不易于存储: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

MongoDB官方文档 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

参考

  1. Leaf——美团点评分布式ID生成系统
  2. Tinyid原理介绍
  3. 9 kinds of distributed ID generation methods, there is always one for you
  4. 发号器设计漫谈
  5. 分布式唯一id:snowflake算法思考
  6. 理解分布式id生成算法SnowFlake