一种分布式唯一主键生成的方式

最近经常被问到分布式唯一主键的生成方式,以前是按照自己的理解去思考怎么生成唯一的分布式主键,最近查看了公司的数据库中间件的源码,发现生成方式挺有趣的,记录一下

一、传统的分布式主键生成的方式

1、Java自带的UUID

UUID通过复杂的规范保证其唯一性。但是其存储为字符串格式,索引效率低。

2、数据库自增id

假如有N台机器(此时设定N=2),两张表分别为T1,T2,不同表的起始值不一样,比如一个起始ID为1,另外一个为2,每次自增N,那么形成的序列就是1,3,5,7,9与2,4,6,8,10等等。但是高并发下性能不佳,主键产生的性能上限是数据库服务器单机的上限。

二、另外的一种生成方式

起初一直以为我司的中间件的全局唯一ID是利用数据库的自增ID,刚好昨天大促,晚上值班的时候看了公司数据库中间件的唯一ID的生成方式,觉得设计得挺巧妙的,记录下。

1、先生成一张表,表结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
 CREATE TABLE sequence(
id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`password` VARCHAR(32) NOT NULL COMMENT '业务库的密码',
`schema` VARCHAR(65) NOT NULL COMMENT 'scheme',
`table` VARCHAR(65) NOT NULL COMMENT '具体表',
`appName` VARCHAR(65) NOT NULL COMMENT '应用名',
minId BIGINT(20) NOT NULL COMMENT '生成ID的最小可以使用的值',
step INT(11) NOT NULL COMMENT '生成ID的步长',
created INT(11) NOT NULL COMMENT '创建时间',
updated INT(11) NOT NULL COMMENT '最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY idx_schema_table(`schema`,`table`)
);

具体解释如下:password是业务库的密码,schema是逻辑库的schema,table是需要生成唯一主键的表名,appName为上层应用的应用名称,minId为当前最小已经分配的ID值,step是不同的应用单次生成分布式主键个数的步长,适配不同的应用的写入并发量的需求,比如订单中心可能每秒钟产生非常多的订单,那步长可以设置为比较大,例如10000,那么单次取能得到10000个主键空间,而如果有的服务QPS较低,可以适量设置为比较小,比如100,避免过度浪费。

2、当应用A完成数据库配置的时候,生成如下的记录

应用记录

3、需要分配ID的时候,比如应用A为order,password是orderpassword,scheme是free,minId起始值为1,step是1000,那么当需要获取分布式主键的时候,需要执行如下SQL,重置minId。

1
update sequence set minId = minId + step where  minId =xxxx and appName = xxx and schame=xxx and table=xxx and password = xxxx

修改之后

从上可以看出该应用分配了step为1000,此时minId = minId + step,然后返回minId,也就意味着每个应用的主键都是自己单条记录的minId控制的。

4、当更新完minId之后,此时获取了一批长度为step的id序列,使用AtmoicLong进行分发与计算,保证安全与唯一。

源代码

IdRangeRound的next()方法,使用了AtomicLong进行获取,并移动内存中可用的ID序列。
next方法

同时为了防止高并发下step设置不合理的问题,会有一条异步线程去提前load一批主键到内存中使用,防止高并发的情况下数据库成为瓶颈。

5、通过这样的方式,一个简单的update指令分配空间,每个业务都被一条记录唯一确定并更新获取。

不得不说做法真是巧妙,三人行必有我师,好好地学习收获,争取变得更好。

坚持原创技术分享,您的支持将鼓励我继续创作!