号段模式如何设计?
简化版
号段模式由数据库按业务 key 原子递增高水位,为实例分配互不重叠的 [start,end],实例在内存中发号;双 buffer 提前申请下一段,降低切段延迟并容忍中心短时故障。
安全关键是分段原子性、高水位永不回退、未用区间不复用,以及耗尽时停止发号而非猜测新区间。
详细版
表中保存 biz_tag、max_id、step、version。申请时执行带行锁或 version CAS 的事务,将 max_id += step,成功实例拥有旧值+1到新值。发号使用 AtomicLong,超出 end 后原子切换 buffer。
当前段剩余 10%~20% 或预计耗尽时间小于申请 P99+重试预算时异步预取。步长按峰值和中心 RTO 计算并可分业务动态调整。数据库需主备/共识;从旧备份恢复后先校准到所有历史已分配上界以上。
完整版教学
一、号段模式解决什么
数据库自增每个 ID 都访问中心,吞吐与可用性受数据库限制。号段把一次协调摊到成千上万个 ID 上,实例本地递增即可。它允许空洞和大致有序,换取高吞吐、较低延迟和较简单的时钟模型。
记忆钩子:数据库只负责切蛋糕且每块不重叠,实例负责在自己那块里逐个分发。
二、表结构与原子申请
CREATE TABLE id_alloc (
biz_tag VARCHAR(64) PRIMARY KEY,
max_id BIGINT NOT NULL,
step INT NOT NULL,
version BIGINT NOT NULL
);
事务读取旧 max=100000、step=10000,更新为 110000;成功后段是 [100001,110000]。并发实例必须通过行锁或 WHERE version=? CAS 串行,先查后改而无锁会分到相同区间。
三、发号与双 Buffer 状态机
CURRENT(100001..110000)
--剩余15%--> LOADING_NEXT(110001..120000)
--current耗尽且next ready--> atomic swap
每个 biz_tag 只允许一个加载任务,避免惊群。next 未准备好而 current 耗尽时,可短暂等待有界时间;仍失败就返回不可用,绝不自行把 end 扩大。
四、步长与可用时间
| 峰值 QPS | step | 单段理论时间 |
|---|---|---|
| 1000 | 10000 | 10s |
| 5000 | 100000 | 20s |
| 20000 | 1200000 | 60s |
目标至少覆盖中心申请 P99 与短时故障,双 buffer 提供额外余量。低频业务用更小 step,动态调整时设置上下限与冷却期。
五、故障恢复和空洞
实例拿到号段后崩溃,未使用部分默认废弃,因此 ID 有空洞。这是避免所有权不确定时重复的安全选择。若尝试回收,必须证明旧进程永久停止并使用 fencing,复杂度通常不值得。
数据库恢复到旧 max_id 会再次分出历史区间。恢复流程应以分配审计记录的“历史已分配上界”为准,或直接跳到事先保存在独立故障域的安全高水位。业务表最大 ID 通常不足以证明安全,因为实例可能仍持有更高但尚未消费完的号段;只有先 fencing 所有旧分配者并证明没有未决区间时,它才能作为辅助证据。数据库备份点本身不是安全发号点。
六、扩展、监控与分库
高并发 biz_tag 可独立行/库,避免一个热行拖累其他业务;跨地域则预分互斥大区间或增加地域命名空间。迁移分配库时确保只有一个权威写中心,并冻结/校验旧库高水位。
监控发号 QPS/P99、current/next 余量、time-to-exhaust、申请失败/CAS 冲突、段切换耗时、step 和浪费。数据表权限应限制人工回退 max_id。
七、常见误区与追问
- 误区:号段用数据库,所以每次 ID 都慢。 数据库只在申请一段时访问。
- 误区:实例崩溃后未用号码应回收。 旧实例所有权不明时复用会重复。
- 误区:数据库主备恢复后可直接继续。 备份中的 max_id 可能低于已发号段。
- 误区:双 buffer 只需两个 AtomicLong。 还需预取状态、singleflight、原子切换和失败处理。
- 追问:号段能保证全局递增吗? 区间递增,但实例并行消费导致生成顺序不严格递增。
- 追问:如何按业务隔离? biz_tag 独立 max/step/指标,热点可分库或专用分配器。
- 追问:中心库挂了怎么办? 消费已持有段;耗尽后停止,恢复后从安全高水位继续。
八、加强记忆
号段模式按“中心原子划段—本地递增—提前预取—耗尽停发—高水位不回退”理解。双 buffer 优化尾延迟,动态 step 平衡中心压力与浪费,空洞是安全代价。真正需要重点防的是区间重叠和恢复后高水位倒退。