关于 sequencer
sequencer 是用于分布式系统设计的技能,帮助工程师根据吞吐量、排序需求、键宽和信息泄露风险选择 ID 生成方案。它比较自增序列、UUID、ULID、Snowflake 和区间分配,并梳理各自的取舍与故障情形。
主要功能
- 01比较五类 ID 生成方案
- 02按排序与吞吐需求选型
- 03评估时钟偏移与计数器溢出
- 04规划位布局、编码和节点 ID
- 05检查分片热点与分配器风险
适用场景
- 01多节点写入时设计主键
- 02评估客户端或离线生成 ID
- 03为高吞吐场景设计紧凑 ID
- 04排查时钟回拨或节点 ID 冲突
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill sequencer安装后在 Claude Code 中输入/sequencer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/deep-dive.mdreferences/providers/aws.mdgcp.mdgeneric.md详细介绍
序列生成器
无需中央瓶颈,即可为所有节点分配唯一标识符;同时还要决定这些 ID 是否必须 可排序 或 单调递增。如果选择不当,问题往往会在后期才暴露,而且代价高昂:冲突会破坏数据,单个分配器会限制写入吞吐量,而泄露创建时间或连续计数的 ID 会暴露业务机密,并助长枚举攻击。
适用场景
系统需要在多个节点上写入新记录,且每条记录都需要主键(订单、消息、上传、事件)。当单个自增列会串行化所有写入、必须在数据库往返前生成 ID(客户端生成、离线生成),或者记录需要大致按时间排序但又不想单独设置排序字段时,适合使用序列生成器。
不适用场景
单个关系型节点仍能轻松满足写入负载(→
back-of-the-envelope)——此时,直接使用 BIGINT AUTO_INCREMENT/SERIAL 就是成本最低且正确的选择;不要为此构建分布式 ID 服务(YAGNI)。
如果已有自然唯一键(邮箱、ISBN、内容哈希),就使用它。除非某项不变量确实需要全局单调性,否则不要强求它——这是这里成本最高的属性,而且通常只需要 每个实体内 有序。
先明确需求
- 生成位置 — 客户端/边缘、应用服务器还是数据库?(决定每个 ID 是否可以接受一次数据库往返。)
- 排序需求 — 无、可按时间排序(允许近似有序),还是严格单调递增?按实体还是全局?这是最关键的分岔点。
- 写入速率和节点数 — 峰值每秒 ID 数,以及生成器数量(→
back-of-the-envelope)。这决定序列计数器所需的位数。 - 大小和编码预算 — 64 位整数(作为索引键时开销低)还是 128 位(完全无需协调),还是短的 URL 安全字符串?
- 信息泄露容忍度 — ID 是否可以泄露创建时间或可猜测的计数(枚举攻击/竞争对手情报)?
可选方案
- 自增 / SQL 序列 — 由一个数据库列分配 ID。适用于由单个节点负责写入,且希望不引入新基础设施的场景。
- UUIDv4(随机 128 位) — 可在任何地方生成,无需协调,碰撞风险实际上为零。适用于只需要唯一性、从不按 ID 排序的场景。
- ULID / UUIDv7(时间前缀的 128 位) — 随机生成,但带有毫秒级时间戳前缀,因此 ID 会按创建时间排序。适用于既要保留 UUIDv4 的零协调特性,又需要按时间排序的场景(新键的现代默认选择)。
- Snowflake 风格(时间戳 + 节点 + 序列,64 位) — 将时间戳、节点 ID 和每毫秒计数器打包进可排序的 64 位整数。适用于写入速率较高,且需要紧凑、近似有序整数键的场景。
- 数据库票据 / 范围分配(Flickr 风格) — 由中央表分配 ID 区间(例如,每次分配 1000 个);每个节点从内存中的区间分配 ID。适用于希望获得简单、近似单调递增的整数,且不想逐个 ID 协调的场景。
权衡
| 选项 | 解决的问题 | 造成的缺点 | 何时更换 |
|---|---|---|---|
| 自动递增 / 序列 | 简单、单调、紧凑的整数 | 写入会串行处理;单节点限制吞吐量;会泄露数量 | 写入量超出单节点承载能力,或需要客户端生成 ID → ticket/Snowflake |
| UUIDv4(随机) | 可在任意位置生成,无需协调,也不会泄露信息 | 128 位;随机顺序会破坏索引局部性(页分裂);不可排序 | 需要按时间排序 → ULID/UUIDv7 |
| ULID / UUIDv7 | 无需协调 + 可按时间排序 + 对索引友好 | 仍为 128 位;只能按毫秒排序(并非严格有序);会泄露创建时间 | 需要 64 位键或严格有序 → Snowflake / 序列 |
| Snowflake 风格(64 位) | 紧凑、近似有序,每节点每秒约 400 万个 ID | 需要分配节点 ID + 处理时钟偏差;纪元/位数预算会限制使用期限 | 时钟同步不可靠,或无法分配节点 ID → ULID |
| 数据库票据 / 范围 | 近似单调的整数、低协调开销、简单 | 分配器表是单点故障;重启后会出现间隙;跨节点只近似有序 | 分配器成为瓶颈或单点故障 → Snowflake/ULID |
压力下的行为
分布式 ID 方案的核心目的,是避免使用单个分配器,因此故障模式主要集中在协调捷径上。
- **分配器成为单点故障/瓶颈(票据、单个序列):**每次写入都会阻塞在同一行/节点上。流量激增或分配器故障会使所有插入停滞。*缓解措施:*分发更大的范围、复制分配器,或改用 Snowflake/ULID(无需经过中心节点)。更大的范围会牺牲单调性,并在重启时浪费 ID。
- **时钟偏差与回拨(Snowflake/时间前缀):**如果节点时钟向后跳(NTP 校正、VM 暂停),它可能会再次发出已用过的时间戳,并在其节点+序列空间内发生冲突。*缓解措施:*在
now < last_timestamp时拒绝发出(阻塞或报错),使用单调时钟源,并针对时钟偏差设置告警。绝不要盲目信任墙上时钟。 - **序列位耗尽:**单个节点在同一毫秒内生成的 ID 数量超过
2^seq_bits时,计数器会溢出。*缓解措施:*自旋等待到下一毫秒,或预先根据峰值速率确定合适的位数预算。 - **节点 ID 冲突:**两个生成器以相同的节点 ID 启动(配置错误、自动扩缩时重复使用),并在无提示的情况下生成重复 ID。*缓解措施:*通过协调器租用节点 ID(→
consistency-coordination),而不是使用静态配置。 - **顺序键导致分片热点:**将单调 ID 用作分片/分区键,会让所有新写入都进入同一分片。*缓解措施:*对键进行哈希或添加前缀分片——分区方案的修复方法见
data-storage。
**监控:**每个节点的 ID 发放速率、时钟偏差/回拨事件、分配器延迟和范围耗尽率,以及重复键错误(应为零)。
如何应用
- 明确输入条件 — 确定生成位置、排序需求
(无 / 可按时间排序 / 严格排序;按实体排序还是全局排序)、峰值 ID/秒、大小
预算和泄露容忍度(参见 先明确)。如果只有一个数据库节点处理写入,就此打住——使用自增 ID(→
back-of-the-envelope)。 - 根据权衡表进行选择 — 不需要排序且 128 位可以接受 → UUIDv4; 需要可按时间排序且无需协调 → ULID/UUIDv7;需要高吞吐量下紧凑的 64 位 k 排序整数 → Snowflake;需要简单的单调递增整数 → 票据/范围。
- 设置关键参数 — 对于 Snowflake,根据节点数和峰值速率确定 epoch 以及时间戳/节点/序列号的位数划分;对于票据,选择区块大小; 确定节点 ID 的分配方式(租约还是静态分配);选择编码方式 (原始整数、Base62、Crockford Base32)。
- 对所选方案进行压力测试 — 检查 压力下的行为:时钟回拨、 序列号耗尽、节点 ID 冲突、分配器故障,以及顺序键对热点分片的影响。确认针对所选配置涉及的每种情况都有缓解措施。
- 用数字估算 — 确认位数预算可覆盖每个节点的峰值 ID 速率,且 epoch 可提供足够的使用年限;确认编码后的长度符合键/URL 限制(→ 关键数字)。
- 选择提供方 — 默认使用通用方案(Snowflake 库、票据行或 ULID/UUIDv7);只有用户指定云平台时才打开对应的提供方文件(参见 选择提供方)。
- 在答案中说明权衡 — 提供一个
Decision | Solves | Worsens | Change it when…表格,每个所选方案对应一行(例如选项、存储方式、故障策略);最后一列填写具体的触发条件(数字或可观测信号)。然后指出最先会出现什么故障。
注意事项
应该
- 对于新键,如果需要无需协调且大致按时间排序,默认选择 ULID/UUIDv7 — 这样可以避免 UUIDv4 的随机索引问题。
- 在选择 Snowflake 之前,根据峰值速率和所需使用年限规划位数预算(时间戳/节点/序列号);记录 epoch。
- 节点会自动扩缩容时,通过协调器租用节点 ID,而不是使用静态配置。
- 时钟回拨时拒绝生成 ID,并针对时钟偏差触发告警;将重复键错误视为 P1。
- 当枚举或信息泄露很重要时,将内部键(可排序,但可能泄露时间)与任何外部不透明 ID 分开。
不应该
- 在数字表明单个数据库节点无法应付之前,不要构建分布式 ID 服务(YAGNI)。
- 不要在热点表上使用随机 UUIDv4 作为聚簇/主索引 — 随机顺序会导致页分裂和写放大。
- 不要在没有复制的情况下,让单个序列或票据行充当整个节点集群的分配器 — 它会成为单点故障和写入瓶颈。
- 不要使用全局单调递增 ID 作为分片键 — 这会造成热点分片(在
data-storage中修复)。 - 不要相信墙上时钟能保证排序;按毫秒排序的 ID 是 k 排序,不是严格排序。
关键数字
64 位 Snowflake 布局(约 41 位时间戳 + 10 位节点 + 12 位序列号)可从其 epoch 起使用约 69
年,支持 1024 个节点,每个节点每毫秒生成 4096 个 ID ≈ 每个节点每秒 400 万个 ID —
足以满足几乎所有单个服务的需求。UUID/ULID 为 128 位 = 16 字节(而 64 位整数为 8 字节),索引键大小翻倍。大小为 N 的票据区块可将分配器调用次数减少 N 倍,但节点重启时最多可能损失 N 个 ID。关于峰值速率和存储容量估算,请参见 back-of-the-envelope;只需重述影响决策的数字。
接口示意
已签发的 ID 是一项契约。应说明其宽度(64 位还是 128 位)、布局
(例如 Snowflake [timestamp:41 | node:10 | seq:12])、排序保证
(无序 / 按毫秒 k 排序 / 严格排序)以及编码方式(原始整数、Base62、
Crockford Base32 — 不区分大小写,适用于 URL)。如有 ID 生成端点,应保持精简:next(entity) -> {id, issued_at}。说明该 ID 是存储键、排序键还是两者皆是 — 该选择会影响 data-storage。
选择提供商
默认采用上述通用方案。如果用户指定了云服务,请阅读
references/providers/<provider>.md,了解托管服务映射、
配额/限制和提供商特有的权衡。如果该提供商没有对应文件,则采用通用方案(大多数云服务没有专用 ID
服务——你需要自行运行库或实现序列)。
图示
要直观展示发号路径(生成器节点 → 64 位布局 → 记录键)或
票据服务的区块分配流程,请使用插件内的 architecture-diagram
技能;快速估算位预算时,内联 [ts | node | seq] 草图就足够了。不要嵌入 Mermaid。
相关构建模块
data-storage— 依赖它:ID 会成为主键/排序键,而顺序键可能导致热点的分片/分区由该模块负责。consistency-coordination— 依赖它获取单调性保证和节点 ID 租约背后的因果关系、顺序与领导者选举理论;请链接到该模块,不要在此重复讲解。messaging-streaming— 与它配合处理消息排序和去重;可按时间排序的 ID 能提供天然的序列和幂等性锚点。api-design— 与它配合:ID 生成是安全重试所需幂等键(由该模块负责)的基础。system-design— 归属概念位于编排器中:推理循环、权衡方法和十种故障模式。
参考资料
references/deep-dive.md— Snowflake 位布局计算和纪元选择、时钟偏移/单调时钟处理、票据服务范围分配、ULID 与 UUIDv7 字节布局、编码(Base62/Crockford)以及节点 ID 租约。设计生成器的细节时请阅读。references/providers/{generic,aws,gcp}.md— 各环境的库/服务映射、限制和常见问题。