分布式全局唯一主键

文章讨论了在MySQL中为什么不推荐使用UUID作为主键,因为UUID长度过长,增加存储成本且降低查询效率。此外,无序的UUID会导致索引分裂和数据页碎片。然后介绍了雪花算法,一种生成趋势递增的分布式ID方法,但面临时钟同步问题和工作ID耗尽的问题。文章还提到了美团Leaf和Tddl-递增分布式Sequence等解决方案,以优化分布式环境下的主键生成策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不推荐uuid等无序序列作为主键

  1.  innodb存储数据的内存是按页管理,每个页16K,区是由连续页组成的空间,大小都是1MB;
  2. 磁盘预读:为了保证区中页的连续性, InnoDB存储引擎一次从磁盘申请4~5个区,,将数据页的相邻的其他的数据页也加载到缓存中, 优先命中缓存再查磁盘。(局部性原理:  在空间上、时间上的相邻存储位置在未来被多次引用)
  3.  缓冲池使用LRU (Latest Recent Used,最长时间没有使用)算法来管理已经读取到未发生变动的页。最频繁使用的页在前端,而最少使用的页在尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。实际上是LRU的变种[冷热数据区innodb_old_blocks_pct + 移动队首间隔时间innodb_old_blocks_time]的方式来降低非热点页带来的负面影响。 
  4. 聚集索引的存储并不是物理连续,只是逻辑连续;由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。主键索引是聚集索引,它的叶子节点是按主键排序的行全部记录,即为整张表的行记录数据,也称为数据页!

UUID是一个128位的字符串,长度过长,会占用大量的存储空间,增加存储成本,同时也会降低查询效率(eg. 原来1页可以存20个,现在只能存10个,那不得再读下一页嘛)

无序序列将导致无法做到总是把新行主键插入到主键索引的最后,此时需要为新行寻找新的合适的位置从而来分配新的空间,这个过程需要做更多额外的操作。

        新增时索引的分裂操作会更无序,影响索引页范围不可控,也将导致数据页记录的频繁乱序移动。

        同时,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片。

这在存储性能、查询效率上都是损耗!

雪花算法 snowflake

在分片规则配置模块可配置每个表的主键生成策略,默认使用雪花算法生成 64bit 的长整型数据。

64bit = 1bit 弃用(默认为0, 符号位) + 41bit 时间戳 (当前时间 - 约定的固定开始时间) + 10bit 的机器Id (5bit dataCenterId + 5bit workId) + 12bit 递增sequence

在末尾生成不碰撞序列时: 如果同一个时间戳毫秒级里sequence溢出了则阻塞至下一个毫秒,开始序列seq重置。

它保证了趋势递增,同时它也有缺陷:

1、在单机上是递增的,但由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。

该缺点可以认为无所谓,一般分布式ID只要求趋势递增, 并不会要求严格递增。 

2、服务器时钟回拨会导致产生重复序列。

解决方案:

  1. 生成器需要记录上一次生成序列的时间,配置一个容忍阈值
    1.  如果时钟回拨的时间超过最大容忍的毫秒数阈值,则程序报错;
    2. 如果在可容忍的范围内,默认分布式主键生成器会等待时钟同步到最后一次主键生成的时间lastTimestamp后再继续创建。
  2. 每次发生时间回拨就切换一次workId。 

3、workId(中间10位)耗尽。

要规划好workId的分发及回收的管理策略!

  1. 有解决方案是: 向前 41bit时间戳 借用几位,来扩张workId的位数,延缓它的耗尽。
  2.  对于分配: 单机:直接默认一个值;分布式:
    1.  数据库表Auto_increment一个值、或 redis递增一个数,然后对 2048 - 1 = 2047 取模。(对N取模取决于要用多少位的机器码)
    2. 将workId值配置在机器系统环境变量里。最好能实现容器化,在各种自动化分配资源时(eg. 自动扩容、滚动发布、故障转移)自动分配业务相关的全局唯一值; 

美团Leaf的解决方案

 Tddl - 递增分布式 Sequence

Tddl - 递增分布式 Sequencehttps://my.oschina.net/u/3434392/blog/3021263icon-default.png?t=N5K3https://my.oschina.net/u/3434392/blog/3021263

  1. 在数据库表里定义业务Sequence序列。相同业务的不同服务实例定义不同的服务序列号(从0开始递增1)。它们每次都从该序列里拿一批ids缓存到本地来递增分配,并更新表内seq序列;
  2. 每个服务的beginVal都是不一样的,通过计算保证每个服务拿到的id都在它隶属的间隔数值区间内按步长递增:  eg. step:1000, A: [0,1000) -> [3000,4000)... , B: [1000,2000) -> [4000,5000)... , C: [2000,3000) -> [5000, 6000)... 
    newBeginVal = oldBeginVal + 服务数*步长, 若oldBeginVal < 数据库序列值,优先将oldBeginVal 调整到该序列值后最近一个它的区间开始值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值