最近经常被问到分布式唯一主键的生成方式,以前是按照自己的理解去思考怎么生成唯一的分布式主键,最近查看了公司的数据库中间件的源码,发现生成方式挺有趣的,记录一下
一、传统的分布式主键生成的方式
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 | CREATE TABLE sequence( |
具体解释如下: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序列。
同时为了防止高并发下step设置不合理的问题,会有一条异步线程去提前load一批主键到内存中使用,防止高并发的情况下数据库成为瓶颈。
5、通过这样的方式,一个简单的update指令分配空间,每个业务都被一条记录唯一确定并更新获取。
不得不说做法真是巧妙,三人行必有我师,好好地学习收获,争取变得更好。