第4章唯一ID生成器——4.4 基于数据库的自增主键的趋势递增的唯一ID

基于数据库的自增主键也可以生成趋势递增的唯一 ID,且由于唯一ID不与时间戳关联,所以不会受到时钟回拨问题的影响。

4.4.1 分库分表架构

数据库一般都支持设置自增主键的初始值和自增步长,以MySQL为例,自增主键的自增步长由auto_increment_increment变量表示,其默认值为1。MySQL可以使用SET命令设置这个变量值,比如SET @@auto_increment_increment=3会将自增步长设置为3。

在创建数据表时,MySQL可以为自增主键指定初始值。例如,如下建表语句会为test_primary_key表的自增主键设置初始值为10:

1
2
3
4
5
create table test_primary_key (
id bigint unsigned not null auto_increment,
col tinyint not null,
primary key (id)
) ENGINE=InnoDB AUTO_INCREMENT=10;

接下来向这个表中插入5条数据,然后看看主键的增长情况,如图4-11所示。

image-20250321220833317

可以看到,第一条数据的主键为10,之后每个主键都自增3。这个数据库功能可以保证数据库分库分表后每个子表的自增主键全局唯一。

如图4-12所示:

image-20250321220907303

假设数据库被水平拆分为5个子表:依次创建5个子表并分别设置自增主键的初始值为1~5,然后设置每个子表的自增主键的自增步长也为5。最终,

  • 子表1生成的自增主键是1,6,11,16,21,…,

  • 子表2生成的自增主键是2,7,12,17,22,…,

  • 其他子表以此类推。

将基于这个思路实现的分库分表架构应用到唯一ID生成器服务,就会生成趋势递增的唯一ID,有效地解决了数据库单点问题,并在一定程度上提高了数据库并发吞吐量。

不过,分库分表架构将自增主键的自增步长与分表个数强行绑定,所以系统整体的可扩展性较差,无法对数据库进行任何扩容操作。也就是说,这个方案只适合不需要扩容的场景。

4.4.2 批量缓存架构

4.2节介绍了基于数据库的自增主键生成单调递增的唯一ID的方案,当时我们讨论过一个关于高可用性的细节问题:如果采用从数据库中批量获取ID的方式,则可以大幅提高系统性能,但是任何时刻只能有一个服务实例工作,否则生成的唯一ID将不是单调递增的,而是趋势递增的。这恰好是我们需要的效果。

既然生成的唯一ID是趋势递增的,那么唯一ID生成器服务可以有任意多个服务实例。如图4-13所示:

image-20250321221115359

每个服务实例都从数据库中批量获取ID并缓存到本地;同时,为了保证数据库主从切换不会生成重复的ID,数据库主从节点采用半同步复制或MGR方式同步最新数据。