分布式下的主键是怎么生成的

SegmentFault

共 1563字,需浏览 4分钟

 ·

2020-11-12 19:42

作者:大军

来源:SegmentFault 思否 




数据库演进之路提到了当数据库的压力瓶颈到了,我们可以采用分库分表来分担数据库的压力,分库分表的情况下,主键是怎么设置的?



数据库主键自增长


正常情况下,如果每个数据库都自增长,那就会出现多个数据库id重复的问题,比如下图所示,都出现了id为1,2,3的主键。



为了避免上述的问题,我们可以在每个数据库设置一个初始值,以及设置每次的增量,如下图所示,第一个数据库初始值是1,增量是3,id就是1,4,7。

虽然避免了ID重复,但是id的递增是没有办法保证的,比如数据库1的id是1,4,7,10。数据库2的id是2,5,数据库3的id是3。而且后面如果要扩容,也是很麻烦。


既然数据库本身没办法生成id,那我们可以用一个专门的数据库生成自增长的id。虽然id可以自增长,不重复,递增,但此时所有的压力都在生成id的数据库上。


为了缓解数据库的压力,可以一次性生成N个主键,比如100,然后存在缓存应用中,数据库每次需要id的时候,去缓存应用取。

缺点就是又多了一层服务。取一个自增长id要缓存服务+数据库。



上面的主键生成用redis行不行?虽然redis速度比较快,但是没有实时持久化,可能造成主键的重复。比如此时为9,incr后变成10,然后挂了,此时还没持久化,再生成id的时候还是9,然后incr为10,就有两个id为10的数据。即便做了故障转移,由于是异步同步数据的,有可能数据还没到slave,master就挂了,此时id还是会重复。

除了数据库的压力,自增长的主键还可能泄露商业机密,别人很容易拆到下一个主键是什么。



UUID


才用数据库生成主键,数据库压力就很大,那可以采用应用来生成。比较简单的就是UUID,性能好,不重复。缺点就是不能保证递增,而且UUID字符串比较长,索引性能很差。



时间戳


以当前毫秒作为主键,优点就是简单、递增,缺点就是可能重复。比如当前毫秒同时有10个并发,此时就重复了。为了减少重复,降低到微秒级别,或者在时间戳后面加个随机字符串,依然有重复的风险。



snowflake


snowflake是twitter开源的分布式ID生成算法,生成64位的bit,第一位是0,保证id是正数。后面41位是当前时间戳的二进制形式,再后面10位机器码的二进制,最后12位是计数顺序号,记录同一毫秒内生成的数量。



比如当前时间为2020-01-01 00:00:00,转换时间戳为1577808000000,再转二进制为10110111101011100101011110111010000000000。此时,前面42位为:


假设当前的机器码为100,转为二进制为1100100,由于不满10位,我们在前面补3个0,结果为0001100100。此时,前面52位为:


如果同一毫秒,同一个机器的并发比较大,此时生成的数据就有重复的,所以snowflake就有后面的12位作为计数器,比如第一个访问的是1,第二个访问的是2,假设我们此时为当前毫秒的第200个获取id的,200转为二进制为11001000,补0后为000011001000,此时,前面64位为:


如果觉得后面的12位不够,我们可以压缩前面机器码的位数,这样计数顺序号的值就可以更大了。

缺点也很明显,由于这个算法是依赖时间戳的,所以当系统的时间回拨的时候,就可能造成id的重复。




点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流。

- END -

浏览 8
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报