当前位置: 首页 > ai >正文

第4章唯一ID生成器——4.1 分布式唯一ID

在复杂的系统中,每个业务实体都需要使用ID做唯一标识,以方便进行数据操作。例如,每个用户都有唯一的用户ID,每条内容都有唯一的内容ID,甚至每条内容下的每条评论都有唯一的评论ID。

4.1.1 全局唯一与UUID

在互联网还未普及的年代,由于用户量少、网络交互形式单调,互联网产品后台数据库使用单体架构就可以满足日常服务的需求。当时每个业务实体都对应数据库中的一个数据表,每条数据都简单地使用数据库的自增主键作为唯一ID。

近年来,随着互联网用户的爆发式增长,数据库从单体架构演进到分库分表的分布式架构,同一个业务实体的数据被分散到多个数据库中。由于数据表之间相互独立,在插入数据时会生成相同的自增主键。此时,如果还使用自增主键作为唯一ID,就会导致大量数据的标识相同,造成严重事故。我们应该保证无论一个业务实体的数据被分散到多少个数据库中,每条数据的唯一ID都是全局的,这个全局唯一ID就是分布式唯一ID。

RFC 4122 规范中定义了通用唯一识别码(Universally Unique Identifier, UUID),它是计算机体系中用于识别信息的一个128位标识符。UUID按照标准方法生成时,在实际应用中具有唯一性,UUID重复的概率可以忽略不计。JDK 1.5在语言层面实现了 UUID,可以轻松生成全球唯一ID:

import java.util.UUID;public class idgenerator {public static void main(String[] args) {String uuid = UUID.randomUUID().toString();System.out.printin(uuid);}
}

UUID的标准格式由32个十六进制数字组成,并通过连字符-分隔成8-4-4-4-12共 36 个字符的形式。例如,6a0d3e6f-allc-4b7d-bb35-c4c530a456b0123e4567-e89b-12d3- a456-426655440000。这种唯一ID的生成方式足够简单,利用本地计算即可生成全球唯一ID。不过,UUID具有一些缺点:

  • UUID字符串需要占用36字节的存储空间,如果每条数据都携带UUID,那么在海量数据场景下存储空间消耗较大。
  • 此外,UUID是无数据规律的长字符串,如果将其用作数据库主键,则会导致数据在磁盘中的位置频繁变动,严重影响数据库的写操作性能。

4.1.2 唯一ID生成器的特点

UUID仅适合数据量不大的场景,比如一个存储集群使用UUID标识每个数据分区。真正可用于海量数据场景的唯一ID生成器,除保证ID不可重复外,还应该具有如下特点。

  • 空间占用小:作为每条数据都携带的字段,唯一ID不应该占用过多的存储空间。
  • 高并发与高可用性:唯一ID生成器是大部分业务服务的重要依赖方,唯一ID的生成操作需要做到高并发无压力,维持长期高可用性。
  • 唯一ID可用作数据库主键:为了不对数据库的写操作造成负面影响,需要保证唯一ID对数据库主键友好。

前两点很好理解,最后一点,什么样的唯一ID才对数据库主键友好呢?我们以MySQL数据库的InnoDB引擎为例。InnoDB使用基于磁盘的B+树表示数据表,并以主键作为索引,即B+树按照主键从小到大的顺序排列数据。

如图4-1所示,B+树的节点使用默认为16KB大小的数据页(Page)表示,其中:

image-20250321180226643

  • 节点分为叶节点和非叶节点,底层是叶节点。
  • 同层数据页之间相互组成双向链表。
  • 非叶节点仅保存N个主键作为指向下一层N个数据页的索引,主键从小到大排列。
  • 叶节点保存实际的数据,叶节点组成的双向链表上的所有数据按照主键从小到大的顺序排列。

使用自增性质的字段作为InnoDB数据表的主键是一个很好的选择,每次写入新数据时,数据都被顺序添加到对应数据页的尾部;一个数据页写满后,B+树自动开辟一个新的数据页。

如图4.2所示,主键值为203的新数据被插入数据页2的尾部,这样一来,B+树将会形成一个较为紧凑的索引结构,空间利用率较高;而且,每次插入数据时也不需要移动已有的数据,时间开销很小。

image-20250321180609696

而如果使用非自增性质的字段(比如身份证号码、电话号码)作为主键,由于主键值较为随机,新数据可能要被插入数据页中间的某个位置。如图4-3所示,主键值为15的新数据只能被插入数据1和数据30之间,数据30、50、90都需要向后移动。

image-20250321180702306

为了给新数据腾出位置,B+树不得不将已有的数据向后移动——如果数据页已满,则会进行多次分页操作。频繁的数据移动和分页操作使得B+树在磁盘上产生大量的碎片,且时间开销很大。因此,官方建议尽量使用自增性质的字段作为InnoDB数据表的主键

为了成为自增性质的主键,唯一ID生成器生成的唯一ID在数值上应该是递增的,这样的唯一ID对数据库主键就是友好的。

占用8字节(64位)的long类型整数适合用作唯一ID,因为:

  • long类型虽然占 用的空间较小,但是可表示的ID范围却非常大
  • long类型整数很容易实现递增的效果。

至此,本章的议题已经明确:设计一个可以生成递增的long类型唯一ID的生成器

4.1.3 单调递增与趋势递增

在正式开始设计唯一ID生成器之前,我们还需要解释一下递增。递增可以分为单调递增趋势递增,从技术实现的角度来看,它们的差异较大。

  • 单调递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则一定有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。如果唯一ID生成器生成的ID单调递增,则说明下一次获取到的ID一定大于上一次获取到的ID。
  • 趋势递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则大概率有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。虽然在一小段时间内数据有乱序的情况,但是从整体趋势上看,数据是递增的。

单调递增和趋势递增的数据特点如图4-4所示。

image-20250321190719896

虽然我们在4.1.2节中已经讨论了唯一ID应该是递增的,但无奈受限于全局时钟、延迟等分布式系统问题,单调递增的唯一ID生成器的设计方案往往会有较大的局限性,与此相比,趋势递增的唯一 ID生成器更受业界欢迎。接下来具体介绍这两种递增类型的唯一ID生成器设计方案的差别。

总结

在分布式架构中,如果还使用自增主键作为唯一ID,会发生什么问题呢?

数据库从单体架构演进到分库分表的分布式架构,同一个业务实体的数据被分散到多个数据库中。由于数据表之间相互独立,在插入数据时会生成相同的自增主键。此时,如果还使用自增主键作为唯一ID,就会导致大量数据的标识相同,造成严重事故。

什么是UUID呢?

UUID的标准格式由32个十六进制数字组成,并通过连字符-分隔成8-4-4-4-12共 36 个字符的形式。例如,6a0d3e6f-allc-4b7d-bb35-c4c530a456b0123e4567-e89b-12d3- a456-426655440000

UUID的缺点?

  • UUID字符串需要占用36字节的存储空间,如果每条数据都携带UUID,那么在海量数据场景下存储空间消耗较大。

  • UUID是无数据规律的长字符串,如果将其用作数据库主键,则会导致数据在磁盘中的位置频繁变动,严重影响数据库的写操作性能。

唯一ID生成器的特点?

  • 空间占用小:作为每条数据都携带的字段,唯一ID不应该占用过多的存储空间。
  • 高并发与高可用性:唯一ID生成器是大部分业务服务的重要依赖方,唯一ID的生成操作需要做到高并发无压力,维持长期高可用性。
  • 唯一ID可用作数据库主键:为了不对数据库的写操作造成负面影响,需要保证唯一ID对数据库主键友好。

为什么使用自增性质的字段作为InnoDB数据表的主键是一个很好的选择?

  • 使用自增性质的字段,每次写入新数据时,数据都被顺序添加到对应数据页的尾部;一个数据页写满后,B+树自动开辟一个新的数据页。
  • 使用非自增性质的字段(比如身份证号码、电话号码)作为主键,由于主键值较为随机,新数据可能要被插入数据页中间的某个位置。为了给新数据腾出位置,B+树不得不将已有的数据向后移动——如果数据页已满,则会进行多次分页操作。频繁的数据移动和分页操作使得B+树在磁盘上产生大量的碎片,且时间开销很大。

为什么占用8字节(64位)的long类型整数适合用作唯一ID呢?

  • long类型虽然占用的空间较小,但是可表示的ID范围却非常大
  • long类型整数很容易实现递增的效果。

什么是单调递增和趋势递增?

  • 单调递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则一定有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。如果唯一ID生成器生成的ID单调递增,则说明下一次获取到的ID一定大于上一次获取到的ID。
  • 趋势递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则大概率有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。虽然在一小段时间内数据有乱序的情况,但是从整体趋势上看,数据是递增的。
http://www.xdnf.cn/news/16094.html

相关文章:

  • 电脑录屏软件推荐:如何使用oCam录制游戏、教程视频
  • ue4 优化
  • 完成多项问题修复,MaxKB开源企业级智能体平台v1.10.9 LTS版本发布
  • Webpack/Vite 终极指南:前端开发的“涡轮增压引擎“
  • 侯捷---c++面向对象高级编程
  • 制造工厂高效出图新技术——共享云桌面
  • JavaSE:学习输入输出编写简单的程序
  • list 介绍 及 底层
  • ESP32使用 vscode IDF 创建项目到烧录运行全过程
  • Flink-1.19.0源码详解8-ExecutionGraph生成-前篇
  • RabbitMQ简述
  • vue3 el-table 列数据合计
  • 在一个网页浏览器的控制台测试后端java是否支持SSE服务
  • Vulnhub Matrix-Breakout-2-Morpheus靶机攻略
  • 基于规则架构风格对业务的重构
  • 每日算法刷题Day52:7.24:leetcode 栈5道题,用时1h35min
  • RPG64.制作敌人攻击波数四:优化
  • 让复杂 AI 应用构建就像搭积木:Spring AI Alibaba Graph 使用指南与源码解读
  • 企业级数据分析创新实战:基于表格交互与智能分析的双引擎架构
  • es0102---语法格式、数据类型、整合springboot、创建库、创建映射、新增数据、自定义查询
  • 【Qt开发】信号与槽(一)
  • node.js中的fs与path模块
  • 主流摄像头协议及其开源情况,GB/T 28181协议介绍
  • 云原生MySQL Operator开发实战(一):Operator基础与CRD设计
  • [语言模型训练]基于 PyTorch 的双向 LSTM 文本分类器实现:基于旅店的评论分类语言模型
  • Java_多线程_生产者消费者模型_互斥锁,阻塞队列
  • Java与NLP实战:文本处理到情感分析全解析
  • Ethereum: 从 1e+21 到千枚以太币:解密 Geth 控制台的余额查询
  • 适配器模式——以springboot为例
  • 《云计算蓝皮书 2025 》发布:云计算加速成为智能时代核心引擎