连载中 10/16

开源选型:从 Leaf 到 UidGenerator

2026-06-10 · 4785 阅读 · 0 评论 · 0 赞

造轮子,还是买现成的

前九篇把原理讲透了:号段双 buffer、雪花时钟、workerID 分配、Redis 两本账。老王回头一看,团队真要手搓一套发号服务,光是 workerID 分配和回拨治理就得写两周——这还是「能跑」的水平,不是「可靠」的水平。他决定先看开源:美团 Leaf、百度 UidGenerator、滴滴 Tinyid,三家大厂各自踩过坑后沉淀的方案。逐个拆。

Leaf:双模式任选的美团方案

Leaf 是三家里功能最全的,一个 jar 包里装了两套引擎,按业务口味切换:

segment 模式:就是第 3、4 篇的 DB 号段 + 双 buffer 落地版。每个业务一个 biz_tag,内存双 buffer 异步预加载。它比原理篇多做了一件事——step 动态调整:根据每个号段的实际消耗速度,把号段长度动态缩放(默认在 10 分钟 QPS 到 100 万 QPS 之间伸缩),流量低谷少占号,高峰自动扩容,DB 压力更平滑。

snowflake 模式:第 5-8 篇的雪花落地版。workerID 交给 ZooKeeper 顺序节点自动分配,解决了「镜像克隆撞号」的人工隐患。但它有个明确的弱项——时钟回拨只告警不兜底:检测到回拨直接抛异常,不等待不切换。也就是说 snowflake 模式的回拨治理,Leaf 把难题留给了你(第 7 篇的三招得自己补)。

接入形态是 Java SDK,应用引入 jar 直连 DB,没有独立发号服务——少一个部署单元,也意味着每个应用都要配号段库的连接。

UidGenerator:RingBuffer 预生成的百度方案

UidGenerator 是雪花改良派。它先动了位分配:64 位切成 1 位符号 + 28 位秒级时间戳 + 22 位 workerID + 13 位序列。秒级时间戳只够用 8.5 年(毫秒级能撑 69 年),换来的是 workerID 手笔阔绰——22 位支持约 420 万节点。每秒序列 8192 个,中等流量绰绰有余,超出就等下一秒,反正秒级单位下等待的感知更粗。

真正的杀手锏是 CachedUidGenerator:启动时把未来一段时间的 ID 批量预生成进 RingBuffer 环形数组,取号就是从内存读一个指针位置,官方压测单机 600 万/s。代价要想清楚两点:一是 RingBuffer 里预生成的 ID,其时间位代表的是「生成时刻」而非「业务领取时刻」,按时间范围反查业务时会和领取时间有偏差;二是重启会丢弃 RingBuffer 里未用完的号,洞比双 buffer 更大。

依赖极简:纯 Java 实现,workerID 用 DB 表或配置分配即可,没有 ZooKeeper。

Tinyid:轻装上阵的滴滴方案

Tinyid 是号段阵营的极简派,只做 segment 模式,但给了两种接入形态:HTTP 方式,任何语言一个 GET 请求拿号,多语言团队的最爱;Java SDK 方式,本地缓存号段异步预加载,发号不出网。它的特色是接口设计——一次调用可以取一个号段、可以批量取 N 个号,号段长度还能按业务动态配置。

代价是没有雪花模式:强依赖 DB,DB 挂了号段打光就停服(降级方案见第 14 篇)。对纯粹要「数据库号段」的场景,它比 Leaf 更轻,代码量小到可以整个读完再决定要不要用。

横评一张表

维度Leaf segmentLeaf snowflakeUidGeneratorTinyid
发号模式DB 号段双 buffer雪花雪花改良 + RingBufferDB 号段
单调/趋势趋势递增毫秒级趋势秒级趋势趋势递增
时钟依赖有,回拨仅告警有,秒级较粗
外部依赖DBDB + ZKDB(分配 workerID)DB
吞吐量级千万级/s(本地号段)单机百万/s单机 600 万/sSDK 千万级/s
多语言不友好(Java SDK)不友好不友好HTTP 友好

决策树一锤定音

老王把选型画成四个问题,按序问下去:

一问:要严格单调吗?三个开源全是「趋势」不是「严格」,严格单调请回到集中式号段 + 单点,或者干脆用 DB 自增。

二问:团队是纯 Java 吗?不是,Tinyid 的 HTTP 形态直接入围;是,往下问。

三问:能接受洞和乱序吗?能,且要极致单机吞吐,UidGenerator Cached 上桌;不能,往叶子走。

四问:时钟运维靠谱吗?靠谱且机器规模可控,Leaf snowflake(补上第 7 篇回拨治理);不靠谱,Leaf segment——号段阵营对时钟零依赖,这正是它经久不衰的原因。

四个问题都落空再谈自研。不过提醒一句:Leaf segment 的核心逻辑不到千行,读完源码再「自研」,多半会发现最后写出来的是个 Leaf 换皮——那就直接用 Leaf。

小结

三大开源一句话:Leaf 双模式功能全,snowflake 回拨弱项要自己补;UidGenerator 吞吐之王,RingBuffer 的洞和时间语义要想清;Tinyid 轻量多语言,纯号段场景性价比最高。选型落定,还有一个隐蔽话题没聊:同样是趋势递增,为什么有的 ID 插表丝滑、有的却把 B+ 树搅得稀碎?下一篇专讲趋势递增的艺术。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式ID#开源发号器#Leaf#UidGenerator#Tinyid

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 3 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞