一个订单号而已,有什么难的?难到美团专门做了个系统来伺候它。今天聊聊它最朴素的思路------一次取一段号。
先说说,"发号"到底难在哪
任何系统都得给数据编个号:订单号、支付流水号、优惠券号......听着简单,数据库自增不就完了?
问题出在量上。当一个系统每秒要发出几万个号,而且要求:
- 每个号全球唯一,不能重复;
- 号一路往上走(这样存进数据库才顺畅、不卡顿);
- 系统几乎不能挂------一年 365 天,总共宕机不能超过 5 分钟。
这时候,让数据库"来一个请求就现编一个号",就彻底扛不住了。传统的做法是每取一个号就读写一次数据库,三个毛病:
- 慢:所有请求都压在那一台数据库上;
- 脆:数据库一挂,全系统都发不出号;
- 难扩容:机器一多,改配置能把你改崩溃。
核心思想:别一个个取,一次取一段

美团想了个特朴素的招。打个比方:
你每次去银行柜台取一个号,跑断腿;不如一次性取回一整本号,回家慢慢用。
Leaf 号段模式就是这么干的------数据库只记一件事: "我已经发到第几号了" 。Leaf 服务定期去数据库一次性搬走一整段号(比如 1000 个),剩下的,全在内存里发,跟数据库就没关系了。
数据库里,每个业务就一行记录:
sql
lua
biz_tag -- 业务名:订单、支付、券,各用各的,互不干扰
max_id -- 已经发到第几个号了
step -- 每次搬多少个号
搬号的时候,一步到位(数据库里这叫"事务",意思是要么全成、要么全不成):
sql
vbnet
UPDATE leaf SET max_id = max_id + step WHERE biz_tag = 'xxx';
SELECT biz_tag, max_id, step FROM leaf WHERE biz_tag = 'xxx';
第一句把"已发到 3000"改成"已发到 4000",第二句把这 1000 个号领走。之后 1000 次请求,全在内存里瞬间完成,数据库碰都不碰。
而且这个方案加机器特别简单:一号机领 1,1000,二号机领 1001,2000,互不打架。想扩容,加机器就行。
灵魂设计:双 Buffer
号段模式爽是爽,但有个要命的瞬间------号段用完的那一刻。
如果只有一个缓冲区,用完时请求线程必须停下来干等数据库返回。网络一抖、撞上个慢查询,所有请求全堵在那儿排队,最慢的那批请求延迟会突然蹿高,用户体验瞬间崩掉。
Leaf 的解法是双 Buffer,规则就两条:
- 当前号段用到 10% ,后台线程偷偷异步去取下一段;
- 当前号段用完,下一段早就备好了,无缝切换。
关键点:发号请求从头到尾不碰数据库,等待的活儿全丢给了后台线程。
🎁 意外收获 :号段长度设成 高峰 QPS × 600(约 10 分钟的量),就算数据库整个挂掉,内存里已经取好的号还能让系统照常发号 10~20 分钟。
动态步长:一个恒等式搞定

双 Buffer 上线后,新问题又来了:
- step 写死,流量涨 10 倍,容灾时间从 10 分钟缩到 1 分钟;
- 流量太小,号段用不完,浪费一大截。
Leaf 的答案是恒等式:
text
ini
Q × T = L
Q = 发号 QPS T = 号段更新周期 L = 号段长度
与其写死 L,不如让 L 跟着 Q 自适应,把 T 稳定在理想区间:
- 号段 15 分钟内 耗尽 →
step × 2,翻倍; - 号段 30 分钟 还没用完 →
step ÷ 2,减半; - 落在 15~30 分钟之间 → 维持不变。
于是"DB 宕机容忍窗口"永远稳定在 10~20 分钟,号段浪费也降到最低。
它不是银弹 ⚠️
边界要拎清楚:
- ID 可被推算------竞对拿两天订单号相减就能估你的单量,敏感场景得换雪花模式;
- 多实例只保证"趋势递增" ,不保证严格单调;
- 强依赖 DB------窗口拉长拉稳了,但 DB 长时间不可用,最终还是停摆。
一句话选型:号段模式 适合订单库主键、消息 ID 等内部场景;雪花模式适合对抗爬虫、防推算的敏感场景(代价是得处理时钟回拨)。
总结
号段模式没有高深算法,就是用最简单的表 + 一个事务 + 两个 Buffer + 一个恒等式,把单台 MySQL 的价值榨到极限:
一台 4 核 8G 的普通服务器,就能做到每秒 5 万次发号,最慢的请求也就 1 毫秒出头。
做架构,很多时候不是"堆更多机器",而是换个思路。把数据库从"频繁逐次读写"里解放出来,让它只做"定期批量记账"------这就是号段模式的精髓。
参考资料 :美团技术博客《Leaf------美团点评分布式ID生成系统》;开源仓库 Meituan-Dianping/Leaf
如果这篇文章帮到了你,点个赞 👍、收个藏 ⭐、关注我,后面持续更新分布式系统的硬核拆解。评论区聊聊你在发号/主键设计上踩过的坑?👇