双 Buffer 机制

双 Buffer 是号段模式的核心优化,提供零等待的 ID 生成体验。

号段模式把取号从”每个 ID 一次”变成”每段一次”,但同步访问发号服务的那一步并没有消失。当本地号段正好用完,下一个请求必须等这次网络往返完成才能拿到新号段,这段空窗期会以延迟尖刺的形式出现在监控曲线上。步长调得越大,空窗期出现得越少,可一旦出现,它就是一次实实在在的接口卡顿。

双 Buffer 的思路是把号段分成”当前使用”和”预加载”两份:Buffer 1 还在发放的时候,后台线程已经替你去申请 Buffer 3;等 Buffer 1 见底,直接切到早已就位的 Buffer 2,切换过程只剩一次内存里的指针交换。这一章要讲清楚的是:步长取多大才够用(步长大则申请少、浪费多,步长小则浪费少、续租频繁)、后台预加载失败时如何兜底、以及 Buffer 使用率要盯到什么程度才算健康。

读完这一章,你应该能画出双 Buffer 的切换时序,说清”零等待”究竟零在哪一步,并结合自己的 QPS 和可接受的 ID 空洞量,给出一套具体的步长与预加载配置。

双 Buffer 解决的是”取号慢”,解决不了”取不到号”——发号服务背后的数据库一旦出问题,号段模式仍然会整体停发。下一节的高可用设计,正是建立在这一节之上。

原理

1. 基本 Buffer

Buffer: [1000, 2000]
分配:1001, 1002, 1003, ...
用完:需要获取新 Buffer

2. 双 Buffer

Buffer 1: [1000, 2000] ← 当前使用
Buffer 2: [2000, 3000] ← 预加载

Buffer 1 用完 → 切换到 Buffer 2
同时预加载 Buffer 3

实现

Java 实现

双 Buffer 在代码里就是两个存放号段的引用加一个后台预加载任务,切换靠一次原子替换完成。

字段:
  current    当前正在发放的号段
  standby    预加载好的备用号段
  preloading 标记,保证同一时刻只有一个预加载任务在跑

取号:
  1. 从 current 取号并把它的剩余量减一
  2. 剩余量见底时,尝试把 current 换成 standby
  3. 换成功 → 立刻发起下一个号段的预加载
     换失败 → 说明别的线程已经换过了,直接重读 current
  4. standby 为空且预加载还没回来 → 退化为同步取号,本次请求会等待

预加载:
  1. 向发号服务申请一个新号段
  2. 成功后写入 standby
  3. 失败则保留原有 standby 并重试,不影响当前号段的发放

性能优势

零等待

单 Buffer:
分配 1000 个 ID
Buffer 用完
等待获取新 Buffer(100ms)
继续分配

双 Buffer:
分配 1000 个 ID
Buffer 用完
立即切换(< 1ms)
继续分配

性能提升

单 Buffer:100,000 QPS
双 Buffer:10,000,000 QPS

性能提升:100x

最佳实践

1. 合理设置步长

步长是号段模式最关键的一个参数:它决定一次申请多少个 ID,也决定了号段切换的频率。步长太小,取号频繁,数据库压力大,切换带来的抖动被放大;步长太大,数据库压力下降,但实例异常退出时这一整段 ID 全部作废,ID 空洞成倍放大。

以单实例 10,000 QPS 为例,步长 1,000 意味着每 100ms 就要取一次号段;步长 10,000 则每秒取一次,切换频率下降 10 倍,代价是最坏情况下浪费接近 10,000 个 ID。

常见做法是按业务类型设置不同步长:写入量大的核心业务用大步长换低数据库压力,量小但要求 ID 连续的业务用小步长换少浪费。步长还要留出余量,避免业务量小幅上涨就把切换频率推到压垮数据库的程度。


2. 后台预加载

预加载的核心是”不让请求线程等”。请求线程只负责从当前 Buffer 取号,发现快见底时发起一个异步预加载请求,然后继续发放;真正的取号动作由后台线程完成,拿到的新号段写进备用槽位。

切换时完全不碰网络:当前 Buffer 取空后,直接把备用槽位提升为当前 Buffer,耗时只有一次指针交换(< 1ms),而单 Buffer 每次都要同步取号(约 100ms),两者相差的就是这一整段等待。

预加载必须做好三件事:同一时刻只允许一个预加载任务在跑,否则会重复申请、浪费号段;预加载失败要保留当前 Buffer 的剩余量并重试,不能让请求线程陪着一起失败;预加载线程的异常要单独告警,因为它不会体现在业务错误率上。


3. 监控 Buffer 使用率

Buffer 使用率 = 当前 Buffer 剩余量 / 步长,是双 Buffer 最重要的业务指标。它同时反映两件事:流量是否超出了步长的预期,以及预加载是否跟得上消耗速度。

观测现象说明处置
使用率稳定下降,切换后回到满值正常,双 Buffer 运转良好保持当前配置
使用率长期低于 10%消耗速度超过预加载速度,或步长偏小调大步长,检查预加载耗时
切换耗时从 < 1ms 明显升高预加载没能在切换前完成,切换退化为等待检查数据库连接与网络
预加载失败次数不为 0发号服务或数据库异常触发告警,必要时降级
ID 空洞数量突增实例异常退出,未发放的号段被丢弃排查实例重启与 OOM

下一步

了解了双 Buffer 机制后,我们学习高可用设计。

👉 下一节:高可用设计

发现内容有问题?

告诉我们哪里需要改进。