分布式 ID
分布式 ID 系统设计指南
一、为什么需要分布式 ID?
1.1 数据库自增 ID 的局限性
核心痛点:
- 单点故障风险:数据库宕机 → 整个系统瘫痪
- 性能瓶颈:所有请求打到单库,TPS/QPS 有物理上限
- 分库分表冲突:多库环境下自增 ID 会重复
- 安全隐患:连续数字暴露业务量(竞争对手可推算真实销售额)
常见错误理解:
“每次生成 ID 都必须向数据库发起 SQL” - 号段模式已优化此问题
1.2 分布式 ID 的核心指标
评估方案时的五个维度:
- 唯一性:跨节点、跨时间绝对不重复
- 有序性:是否支持单调递增(影响数据库索引性能)
- 可用性:故障时的影响范围
- 性能:QPS 上限与延迟(微秒级 vs 毫秒级)
- 扩展性:适应业务变更的能力
二、主流方案对比
2.1 UUID
- 优点:本地生成、无网络开销
- 缺点:无序导致数据库索引性能差、字符串占用空间大
- 适用场景:临时令牌、非主键标识
2.2 数据库自增/Redis 自增
- 优点:实现简单、ID 有序
- 缺点:中心化依赖、高并发下性能受限
详解: | 方案 | 瓶颈原因 | QPS 上限 | |——|———-|———| | DB 自增 | 磁盘 I/O + 锁竞争 | ~5000 | | Redis 自增 | 网络 I/O + 单线程 | ~10 万 |
2.3 雪花算法(Snowflake)
- 结构:1 位符号 + 41 位时间戳 + 10 位机器 ID+12 位序列号
- 优点:本地生成、高性能、趋势递增
- 缺点:强依赖时钟,存在时钟回拨风险
- QPS:单机百万级
2.4 美团 Leaf / 百度 UidGenerator
工业级解决方案,解决了原生雪花的痛点
| 特性 | Leaf-Snowflake | Leaf-Segment | 百度 UidGenerator |
|---|---|---|---|
| 依赖 | ZooKeeper | MySQL | RingBuffer 内存 |
| 性能 | 5 万 + QPS | 5 万 + QPS | 600 万 + QPS |
| 容灾 | ZK 持久化 | 双 Buffer 机制 | 预发号缓存 |
| 运维成本 | 中 | 低 | 中 |
| 适用场景 | 容器化环境 | 核心交易 | 海量日志 |
2.5 三种 ID 对数据库(B+ 树)的影响
前提:主键索引底层是 B+ 树,页满就要分裂、树变深——ID 的有序性直接决定写库时是”排队”还是”插队”。
| 对比项 | 自增 ID | 雪花 ID | UUID |
|---|---|---|---|
| 插入位置 | 永远末尾追加 | 末尾追加(趋势递增) | 随机落任意叶子页中间 |
| 页分裂 | 极低,只尾页分裂 | 低,只尾页分裂 | 高,到处分裂,可能连锁到父层、树加高 |
| 分裂后空间 | 旧页是满的,不浪费 | 同左 | 两页都半满,浪费 |
| 索引体积/性能 | 最小、最快 | 小、快 | 大(存字符串),又厚又松,写库随机 I/O |
一句话记忆:自增/雪花 = 排队(有序追加,几乎不分裂);UUID = 插队(随机乱插,整棵树到处都是半空的页)。
结论:
- 能用自增就用自增;要全局唯一就用雪花这类趋势递增 ID,效果最接近自增
- 删除/更新多也会留半空页(碎片)→ 定期
OPTIMIZE TABLE压实
三、核心技术原理
3.1 雪花算法的时钟回拨问题
什么是时钟回拨?
当 NTP 时间同步服务器将本地时钟往回调整时,雪花算法会生成重复 ID。
三种解决策略
- 轻度回拨(≤5ms):原地等待(Sleep),等时间追平
- 中度回拨:动态切换 WorkerID(需放大 WorkerID 位数至 22 位)
- 重度回拨(>5ms):直接抛异常,触发告警
为什么 Leaf 官方选择”宁死不报假号”?
- “绝对不重复”是底线,优先级高于”高可用”
- 自动换 WorkerID 需要清空内存缓存、协调 ZK、持久化状态,实现复杂且容易出错
- 时钟回拨是服务器异常,应该告警修复根因,而不是自动绕过掩盖问题
业界加固方案(二次开发)
| 方案 | 原理 | 代价 | |——|——|——| | 机器 ID 换绑 | 向 ZK 申请新 WorkerID | 压缩时间戳/序列号位数 | | 序列号扩展 | 增加”回拨次数标记位” | 降低单毫秒并发上限 | | 时间持久化 | 启动前校验,偏差大则拒绝启动 | 需配合负载均衡摘除节点 |
3.2 号段模式(Segment)的双 Buffer 机制
痛点
没有双 Buffer 时:号段用尽 → 同步等数据库 → 阻塞所有业务请求
运作流程
graph LR
A["Buffer A<br/>当前抽屉"] -->|"业务发号使用"| B["消耗到 10% 阈值"]
B -->|"触发"| C["后台异步<br/>从数据库进货"]
C --> D["提前装满<br/>Buffer B"]
D --> E["瞬间切换到 B<br/>角色互换"]
E --> A
核心价值
- 消灭毛刺:数据库 IO 被完全异步化
- 掩盖延迟:业务方永远感知不到”进货”动作
- 容灾能力:即使 DB 宕机,内存号段仍能支撑一段时间
3.3 WorkerID 分配机制
Starter 模式(内嵌)
- 每个业务实例各自连 ZK 领 WorkerID
- 首次获取后缓存在本地文件,ZK 宕机不影响已运行服务
- 缺点:N 个服务 × M 个实例 = ZK 连接数多
独立部署模式
- 只有 Leaf 服务连 ZK,业务服务不感知
- ID 生成中心化为独立服务,统一管控
- 缺点:每次调用产生网络消耗(RPC/HTTP)
- 优点:ZK 连接数少,只管理 Leaf 集群
四、选型决策指南
4.1 场景匹配矩阵
| 业务类型 | 推荐方案 | 理由 |
|---|---|---|
| 电商订单 | Leaf-Segment | 数据库索引友好、性能足够 |
| 支付流水 | Leaf-Segment | 强一致性、趋势递增 |
| 日志追踪 | Leaf-Snowflake | 超高并发、不依赖数据库 |
| 消息队列 | Leaf-Snowflake | 无网络 IO、纯内存计算 |
| K8s 容器环境 | Leaf-Snowflake | WorkerID 自动分配 |
| 对账审计 | Leaf-Segment | 严格递增可追溯 |
4.2 架构权衡要点
独立部署 vs Starter 模式
| 维度 | 独立部署 | Starter 内嵌 | |——|———-|————-| | 网络消耗 | 有(1~5ms) | 无(纳秒级) | | 运维成本 | 高 | 低 | | 性能瓶颈 | 网络带宽 | CPU 计算 | | 架构解耦 | 完全解耦 | 业务耦合 | | 最佳规模 | >10 个服务 | <5 个服务 |
何时必须用独立部署?
- 公司级统一 ID 管控需求
- 几十个微服务需要集中管理
- 需要独立监控/调优 ID 生成参数
4.3 安全加固方案
防业务量泄露
问题:连续自增 ID → 竞争对手可通过差值推算订单量
解决方案:
- 内外 ID 隔离(大厂标配)
1 2 3 4 5 6
// 内部存 Leaf ID(自增) long internalId = leafService.getSegmentId(); // 对外返回加密字符串(Hashids) String externalId = hashIds.encode(internalId); // 输出:xY7bQ (无法推测)
- 超大起始值 + 随机步长
1 2 3
-- 初始 max_id 设为天文数字 UPDATE leaf_alloc SET max_id = 100000000; -- 步长可调为动态随机值
- 接口层防越权(必须实施)
1 2 3 4 5 6
// ❌ 错误:仅凭 ID 查询 SELECT * FROM orders WHERE id = 10001; // ✅ 正确:用户 ID 联合校验 SELECT * FROM orders WHERE id = 10001 AND user_id = currentUser.id;
五、常见误区澄清
5.1 NTP 与时间回拨的关系
误区:”NTP 可以解决时钟回拨”
真相:NTP 是导致回拨的诱因之一,而非解决方案
1
2
正确理解链:
硬件漂移 → NTP 校准 → 时间回调 → Leaf 检测回拨 → 执行防御策略
5.2 号段模式的连续性
误区:”号段模式 ID 绝对连续”
真相:服务重启会导致 ID 空洞(未使用的号段丢失)
1
2
3
4
5
举例说明:
正常运行:[1001, 3000] → 用到 1050
服务挂掉:内存清空,损失 1950 个 ID
重启后:基于 DB 的 max_id=3000 重新申请 [3001, 5001]
结果:ID 从 1050 跳到 3001,中间断号
影响评估:
- ✅ 订单/交易:完全可以接受
- ⚠️ 财务流水:需额外标注序号偏移
- ❌ 发票/排队号:不能用分布式 ID
5.3 分布式 ID vs 分布式锁
完全不同:
- 分布式 ID:全局唯一标识(如订单号)
- 分布式锁:并发控制(如秒杀扣库存)
关联点:分布式 ID 可作为锁的唯一标识,但不能替代锁机制
六、面试应答模板
Q:为什么不用数据库自增 ID?
回答结构:
- 风险:单点故障、性能瓶颈、分库冲突
- 对比:展示 Leaf/雪花在 QPS 上的量化优势
- 案例:美团日均亿级调用,Leaf 处理 99.999% 可用性
Q:雪花算法如何解决时钟回拨?
回答要点:
- 轻度回拨:5ms 内等待
- 重度回拨:抛异常 + 告警
- 业界增强:WorkerID 换绑或序列号扩展
Q:号段模式和雪花模式怎么选?
回答框架:
1
2
3
4
5
6
7
8
9
10
11
1. 先判断业务特征:
- 需要严格递增?→ 选 Segment
- 极致性能优先?→ 选 Snowflake
2. 再考虑技术栈:
- 不想运维 ZK?→ 选 Segment
- 已经在用 K8s?→ 选 Snowflake
3. 最后混合策略:
- 核心业务(订单):Segment
- 辅助业务(日志):Snowflake
附录:参考资源
- 美团 Leaf 官方:https://github.com/Meituan-Dianping/Leaf
- 技术文章:https://tech.meituan.com/2019/03/07/open-source-project-leaf.html
- 百度 UidGenerator:https://github.com/baidu/uid-generator