SoupCalc

Snowflake ID 生成器:深入解析 Twitter 的 63 位分布式标识符

什么是 Snowflake ID?

Snowflake ID 是一个 63 位整数,用于在分布式系统中生成唯一标识符,无需中央协调节点。Twitter 于 2010 年开发了此格式,以替代无法在数十台向分片 MySQL 集群写入的应用服务器之间扩展的自增数据库序列。每个 Snowflake ID 都是一个有符号的 64 位整数,其中最高有效位始终为零,剩余 63 位有效载荷分为四个字段:一个 41 位的时间戳偏移量、一个 5 位的数据中心标识符、一个 5 位的机器标识符和一个 12 位的序列号。这种布局使得单个 Snowflake 服务节点每毫秒最多可生成 4,096 个唯一 ID,从而使系统在部署数百个工作进程的情况下具备每秒数百万个 ID 的理论吞吐量。

Snowflake ID 是单调递增的——在同一纪元内,每个新 ID 都比上一个更大——但它们在不同节点之间并非严格有序。在同一毫秒内运行的两台机器所产生的 ID 会交错排列,这是一种有意的权衡:该方案以牺牲完美排序为代价,换取绝对的运行独立性。每个工作节点只需知道自己的数据中心和机器编号,以及当前时间,就可以生成全局唯一的标识符,无需询问任何对等节点或锁服务。

63 位标识符的结构剖析

Snowflake ID 是一个 63 位整数,由以下字段组成,从最高有效位到最低有效位依次为:

  • 41 位时间戳——自自定义纪元以来经过的毫秒数。
  • 5 位数据中心 ID——标识承载该工作节点的物理数据中心。
  • 5 位机器 ID——标识该数据中心内的单个主机或进程。
  • 12 位序列号——一个每毫秒计数器,当时钟向前推进时归零。

描述此布局的准确表述是:一个 41 位的时间戳、5 位的数据中心、5 位的机器和 12 位的序列号。这些字段总共占用 63 位,前导符号位设为零,以便 ID 在 Java 和 C# 等语言中能舒适地容纳于有符号的 64 位整数内。

Twitter 纪元与时间戳组件

每个 Snowflake ID 都携带着相对于自定义纪元的时间戳偏移量:1288834974657 毫秒。该值约对应 2010-11-04T04:42:54.657Z,被选为与 Twitter 内部 Snowflake 部署的开始时间对齐。使用自定义纪元而非 Unix 纪元,意味着 41 位时间戳字段可以表示约 69 年的范围才会溢出。该字段能容纳的最大时间戳值为 2^41 减 1,即 2,199,023,255,551 毫秒——约为纪元后 69.7 年,将翻转日期推迟到 2080 年代。

要编码时间戳,从当前 Unix 纪元毫秒数中减去纪元,然后将结果左移 22 位,为三个较低字段腾出空间。任何早于纪元的输入时间戳均无效,必须被拒绝。

数据中心与机器节点标识

5 位数据中心字段和 5 位机器字段各自接受 0 到 31 的值,在整个部署中总共产生 1,024 个唯一节点标识符。工作节点在启动时被分配一个数据中心 ID 和一个机器 ID,通常通过配置文件、ZooKeeper 等协调服务或命令行参数进行。该分配在集群内必须唯一:两个共享相同(数据中心,机器)对的工作节点将产生冲突的 ID,除非它们的时钟不同步。

这些字段通过将数据中心值左移 17 位、机器值左移 12 位,然后将移位后的值通过 OR 运算合并到 63 位整数中来插入 ID。1,024 的节点总数对于大多数实际部署来说已足够,但需要超过 1,024 个工作节点的环境可以从序列号字段或时间戳字段中借用位数,代价是吞吐量或纪元范围的缩减。

序列计数器与时钟边界

12 位序列号字段是 Snowflake 设计的核心。它从 0 到 4,095 运行,并在同一毫秒内每生成一个 ID 递增一次。当序列号达到 4,095 时,工作节点进入忙循环等待,直到系统时钟前进到下一毫秒,此时序列号归零,生成恢复。这使得单个节点每毫秒的最大突发速率为 4,096 个 ID。

时钟漂移和时钟回拨是 Snowflake 算法最显著的两个故障模式。如果系统时钟向后移动——无论是由于 NTP 校正、虚拟机迁移还是手动更改——工作节点可能生成时间戳小于上一个所生成 ID 的 ID,从而破坏唯一性保证。标准的缓解措施是,当检测到时钟回拨时停止 ID 生成并报错,拒绝服务请求,直到时钟赶上最后记录的时间戳。无法容忍停机的部署应考虑使用专用时间服务或逻辑时钟层。

计算示例

考虑以下输入值:Unix 纪元时间戳为 1700000000000 毫秒,数据中心 ID 为 7,机器 ID 为 13,序列号为 4095。

编码。 首先计算时间戳偏移量:1700000000000 减去 Snowflake 纪元 1288834974657 等于 411165025343。将此偏移量左移 22 位得到 1724551110456246272。将数据中心值 7 左移 17 位得到 917504。将机器值 13 左移 12 位得到 53248。序列号无需移位。通过按位 OR 将四个项合并,生成最终的 Snowflake ID:1724551110457221119。

解码。 要从 ID 1724551110457221119 中恢复原始字段,屏蔽最低 12 位以提取序列号:4095。右移 12 位并屏蔽最低 5 位以恢复机器 ID:13。右移 17 位并屏蔽最低 5 位以恢复数据中心 ID:7。右移 22 位以获得时间戳偏移量:411165025343。加上纪元 1288834974657 以恢复原始 Unix 纪元时间戳:1700000000000。

往返过程完全精确,因为每个字段都适合其分配的位宽,编码过程中不会丢失任何信息。

精度与局限性

只要所有输入值都在其有效范围内,Snowflake 编码方案就是确定性的且可逆的。时间戳偏移量必须是非负的 41 位整数;数据中心和机器 ID 必须在 0 到 31 的范围内;序列号必须在 0 到 4,095 的范围内。任何超出这些边界的输入都将被拒绝为无效。

该方案不考虑闰秒或亚毫秒精度。它依赖主机系统时钟,而时钟可能会漂移或被外部进程调整。在系统时钟因大幅 NTP 跳跃而向前调整的部署中,偏移量字段会正确前进,但序列计数器在此期间处于空闲状态,留下了一段未发放 ID 的时期。这对唯一性无害,但会在 ID 时间线上产生不连续性。

共享相同(数据中心,机器)元组的网络分区工作节点,如果它们同时处于活动状态,将产生冲突的 ID。该标识符空间提供 1,024 个唯一节点槽位,超出此数量需要自定义的位布局变体。

来源

编辑记录

本文描述了 Twitter 2010 年参考实现中定义的 Snowflake ID 格式。位布局和纪元值直接取自开源的 Scala 源代码。计算示例已通过手动计算进行验证,对相同值进行编码和解码以确认往返正确性。关于时钟回拨和节点限制的讨论反映了分布式 ID 生成工程文献中记录的操作经验。

本文不涵盖第三方实现或更改位布局的变体——例如使用 10 位机器 ID、不同纪元或从协调服务中提取的工作节点 ID 字段的变体。这些变体超出了原始 Snowflake 规范的范围。 作者:SoupCalc 编辑团队 最后审阅日期:2026 年 8 月 11 日。