一篇读懂分布式 ID:UUID、雪花算法与号段模式

单体时代,数据库的自增 ID(AUTO_INCREMENT)就是唯一标识——简单、有序、够用。微服务与分库分表时代,自增 ID 不再够用:多个数据库实例的自增序列会冲突,跨服务的 ID 无法保证唯一——需要一个跨服务、跨数据库的全局唯一 ID 生成方案。三种主流方案——UUID、雪花算法、号段模式——各有取舍。

方案一:UUID——最简单但不最优

UUID(Universally Unique Identifier)是 128 位的随机标识符(标准格式如 550e8400-e29b-41d4-a716-446655440000),生成算法保证全球唯一(基于时间戳 + MAC 地址 + 随机数——UUID v4 完全随机)。优势:生成极简(一行代码 UUID.randomUUID()),无网络调用(本地生成),全球唯一(不需要协调)。劣势:无序——UUID 是随机的,作为 MySQL 的主键会导致 B+ 树频繁页分裂(随机插入导致页分裂与碎片——写入性能差);太长——128 位 = 36 个字符(含连字符),作为索引占用空间大;无业务含义——从 UUID 中无法提取任何信息(创建时间、来源等)。适用场景:对 ID 无排序要求、不需要存入数据库索引的场景(如分布式链路追踪的 traceId、临时文件名)。不适合作为数据库主键。

方案二:雪花算法(Snowflake)——Twitter 的经典设计

雪花算法的结构是一个 64 位的长整数,分为四段:符号位(1 位,恒为 0)+ 时间戳(41 位,毫秒级,可用 69 年)+ 机器 ID(10 位,支持 1024 个节点)+ 序列号(12 位,每毫秒可生成 4096 个 ID)。生成过程:取当前时间戳,拼上机器 ID 与同一毫秒内的递增序列号——纯内存操作,无网络调用,单机每秒可生成四百万个 ID。优势:趋势递增(时间戳在高位,ID 按时间排序——MySQL B+ 树顺序插入友好);高性能(本地生成无网络调用);可解码(从 ID 中可提取创建时间——本库语境的「ID 自带元信息」)。劣势与解决:时钟回拨——服务器时钟被回拨(NTP 校时)时可能生成重复 ID——解法是等待时钟追上、或使用序列号空间缓冲回拨期间的请求;机器 ID 分配——10 位机器 ID 需要手动分配或通过 ZooKeeper 自动分配(服务启动时注册临时节点获取序号)。雪花算法是分布式 ID 的工业标准——各大厂的 ID 生成服务几乎都是它的变体。

方案三:号段模式——数据库辅助的折中

号段模式的机制:从数据库批量获取一段 ID(如 1-10000),缓存在应用内存中逐步分配——用完后再取下一段。数据库表结构:biz_tag(业务标识)+ max_id(当前已分配的最大值)+ step(每次获取的步长)。获取 ID 的流程:UPDATE id_segment SET max_id = max_id + step WHERE biz_tag = 'order' → 读取新的 max_id → 应用在内存中从 max_id - step + 1 到 max_id 逐个分配。优势:ID 严格递增(同一业务标签内)、数据库压力低(每次取一批,不是每个 ID 都查库)、实现简单。劣势:服务重启后 ID 有空洞(缓存中未用完的 ID 丢失)、双 buffer 优化(用完 10% 时异步预取下一段——消除「取号段」的延迟毛刺)。适用场景:对连续性有要求的场景(订单号需要连续)、已有数据库且不想引入额外组件。

选型框架

选型的三个维度。有序性——数据库主键需要趋势递增(B+ 树顺序插入),UUID 不满足、雪花与号段满足。性能——本地生成(UUID、雪花)无网络调用,号段需要偶尔访问数据库。运维复杂度——UUID 最简、雪花需要机器 ID 分配与时钟管理、号段需要数据库表维护。选型的决策树:需要存入数据库做主键 → 雪花算法或号段模式(不用 UUID);需要极致性能(无网络调用) → 雪花算法;需要连续 ID → 号段模式;对 ID 无特殊要求 → UUID(最简单)。一句话总结:雪花算法是分布式 ID 的默认选择,号段模式是「不想引入新组件」的折中,UUID 是「不存数据库」时的便利选项。

分布式 ID 的「业务含义」:ID 不只是唯一标识

分布式 ID 的设计还有一个维度:ID 本身是否携带业务信息。雪花算法的 ID 包含时间戳(从 ID 可以反解出创建时间——「这个订单是什么时候创建的」不需要查数据库);UUID 不含任何业务信息(纯随机——创建时间、来源、类型都无法从 ID 中获取)。业务信息的价值:快速定位(从 ID 直接获取创建时间可以快速定位时间段)、减少查询(ID 自带的元信息不需要额外查询)、分表的依据(按时间范围分表时,ID 中的时间戳可以作为路由的依据)。但不建议过度编码业务信息到 ID 中——ID 的首要职责是唯一性,过度编码会让 ID 的生成逻辑复杂化且 ID 的格式变更成本极高。平衡点:ID 中可以包含时间戳(雪花算法已内置)但不应该包含业务状态(如订单状态编码到 ID 中——状态会变而 ID 不会变)。

分布式 ID 的「时钟回拨」处理:雪花算法的最大隐患

雪花算法的最大隐患是时钟回拨(clock drift backwards):服务器的时钟因为 NTP 校时被回拨(例如从 10:00:05 回拨到 10:00:03),雪花算法基于时间戳生成 ID——回拨后的时间戳可能生成与之前相同的 ID 导致重复。应对策略:方案一等待——回拨时间短(几秒内)则等待时钟追上后再恢复生成;方案二使用备用机器 ID——切换到另一个机器 ID 继续生成(消耗一个备用机器 ID 的额度);方案三记录最后使用的时间戳——如果检测到回拨则拒绝生成并告警。Redis 的分布式锁与本库语境的类似问题:Redis 的 SET NX PX 也依赖时间戳(过期时间的计算)——时钟回拨同样会导致锁的过期时间计算错误——本库《分布式锁》的看门狗机制也会受影响。时钟回拨是分布式系统的通病——NTP 校时的策略(step vs slew)应该在整个基础设施中统一。

分布式 ID 的业务含义与选型总结

分布式 ID 的设计还有一个维度:ID 本身是否携带业务信息。雪花算法的 ID 包含时间戳从 ID 可以反解出创建时间不需要查数据库。UUID 不含任何业务信息纯随机。业务信息的价值:快速定位减少查询分表的依据。但不建议过度编码业务信息到 ID 中——ID 的首要职责是唯一性。选型总结:雪花算法是分布式 ID 的默认选择号段模式是不想引入新组件的折中 UUID 是不存数据库时的便利选项。

分布式 ID 的时钟回拨处理

雪花算法的最大隐患是时钟回拨服务器的时钟因为 NTP 校时被回拨雪花算法基于时间戳生成 ID 回拨后的时间戳可能生成与之前相同的 ID 导致重复。应对策略方案一等待回拨时间短则等待时钟追上后再恢复生成。方案二使用备用机器 ID 切换到另一个机器 ID 继续生成。方案三记录最后使用的时间戳如果检测到回拨则拒绝生成并告警。时钟回拨是分布式系统的通病 NTP 校时的策略应该在整个基础设施中统一。

雪花算法的「机器 ID 分配」方案

雪花算法的 10 位机器 ID 需要在服务启动时分配——分配方案有三种。方案一:手动配置——每个实例配置一个唯一的机器 ID(通过环境变量或配置文件),简单但需要手动管理(实例多了之后容易冲突)。方案二:ZooKeeper 分配——服务启动时在 ZooKeeper 中注册临时顺序节点,序号即机器 ID——自动分配但依赖 ZooKeeper。方案三:数据库分配——使用数据库表存储机器 ID 的分配记录(服务启动时 INSERT 或 UPDATE 获取一个未使用的 ID)。三种方案的选型:小规模部署手动配置足够、大规模或频繁扩缩容用 ZooKeeper 或数据库分配——机器 ID 的分配是雪花算法落地的第一个工程决策。

分布式 ID 与数据库分片:分片键的选择

分布式 ID 与数据库分片的结合有一个关键决策:分片键的选择。分片键如果选择 ID 本身(按 ID 的哈希值分片),ID 的趋势递增特性会导致数据集中在新分片(热分片问题——最新的分片承载所有新写入)。解法是使用基因法:将分片键的信息嵌入到 ID 中(如雪花算法的机器 ID 部分用作分片路由)——ID 的生成时就确定了它属于哪个分片。另一个方案是使用单独的分片键(如 user_id):同一用户的订单在同一分片(用户的查询不需要跨分片),但全局的范围查询需要跨分片。分片键的选择是分库分表最重要的设计决策——一旦确定很难变更——需要在业务查询模式与数据分布均匀性之间找到平衡。

小结

分布式 ID 收成四条:UUID 最简但不适合作数据库主键——无序导致 B+ 树页分裂、太长占用索引空间;雪花算法是工业标准——64 位 = 时间戳 + 机器 ID + 序列号,趋势递增且高性能,需处理时钟回拨与机器 ID 分配;号段模式从数据库批量取 ID——严格递增、实现简单,但服务重启有空洞;选型决策树——数据库主键选雪花或号段、不存数据库选 UUID、需要连续 ID 选号段。分布式 ID 的本质:在「全局唯一」的前提下,权衡「有序性、性能、运维复杂度」——没有全能方案,只有场景匹配。

参考资料

  • Twitter Snowflake GitHub —— 雪花算法的原始设计
  • 美团 Leaf GitHub —— 号段模式 + 雪花算法的混合实现(美团点评开源)
  • 本库前篇:《海量定时任务中间件》《缓存一致性实战》
← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?