从目标 → 主流方案 → 对比 → 实战选型 → 源码级思想 ,适合 P6~P7 / 架构岗,可以直接背,也可以当系统设计题来答。
一、分布式 ID 的设计目标(面试开场)
分布式 ID 是跨服务、跨库、跨机器的唯一标识,设计时要满足:
✅ 全局唯一(最基本)
✅ 趋势递增(MySQL B+Tree 友好)
✅ 高性能(高并发下不成为瓶颈)
✅ 高可用(不能依赖单点)
✅ 易接入(业务无感)
✅ 可解析(排查问题方便)
二、常见分布式 ID 方案总览
| 方案 | 唯一性 | 递增 | 性能 | 复杂度 | 推荐指数 |
|---|---|---|---|---|---|
| UUID | ✅ | ❌ | ✅ | ⭐ | ⭐⭐ |
| 数据库自增 | ✅ | ✅ | ❌ | ⭐ | ⭐⭐ |
| 数据库号段 | ✅ | ✅ | ✅ | ⭐⭐ | ⭐⭐⭐⭐ |
| Redis 自增 | ✅ | ✅ | ✅ | ⭐⭐ | ⭐⭐⭐ |
| Snowflake | ✅ | ✅ | ✅✅ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Leaf / TinyID | ✅ | ✅ | ✅✅ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
三、主流方案详解(重点)
1️⃣ UUID(不推荐)
UUID.randomUUID().toString();
优点
✅ 简单
✅ 无网络开销
✅ 本地生成
缺点(致命)
❌ 无序 → MySQL 页分裂
❌ 太长(36 位字符串)
❌ 无业务含义
❌ 不易排查问题
👉 结论:只适合临时标识,不适合业务 ID
2️⃣ 数据库自增 ID(单机 OK)
CREATE TABLE order (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
优点
✅ 简单
✅ 严格递增
缺点
❌ 单点
❌ 性能瓶颈
❌ 分库分表困难
👉 结论:单体系统可用,分布式不行
3️⃣ 数据库号段模式(推荐)
本质是:批量取号,内存发号
原理
DB 只存最大值
每次取 1000 个号
缓存在内存中
用完再取
表结构
CREATE TABLE id_segment (
biz_tag VARCHAR(32) PRIMARY KEY,
max_id BIGINT,
step INT
);
发号逻辑
update id_segment
set max_id = max_id + step
where biz_tag = 'order';
优点
✅ 高性能
✅ 趋势递增
✅ 弱依赖 DB
缺点
❌ 依赖 DB
❌ 重启可能浪费 ID
✅ 中小规模系统非常合适
4️⃣ Redis 自增(简单但有限)
INCR order:id
优点
✅ 快
✅ 简单
✅ 原子性
缺点
❌ Redis 宕机风险
❌ 重启可能重复(需持久化)
❌ 无时间戳信息
👉 适合非核心业务
5️⃣ Snowflake(最主流 ⭐⭐⭐⭐⭐)
Twitter 开源,工业级方案
ID 结构(64 bit)
| 1 bit | 41 bit 时间戳 | 10 bit 机器ID | 12 bit 序列号 |
0 | 0000000000000000000000000000000000000 | 0000000000 | 000000000000
各部分含义
| 位段 | 长度 | 说明 |
|---|---|---|
| 符号位 | 1 | 固定 0 |
| 时间戳 | 41 | 毫秒级 |
| 机器 ID | 10 | 数据中心 + 工作节点 |
| 序列号 | 12 | 同一毫秒内自增 |
特点
✅ 趋势递增
✅ 本地生成
✅ 高性能
✅ 无依赖
问题点(面试必问)
| 问题 | 解决方案 |
|---|---|
| 时钟回拨 | 等待 / 抛异常 / 用上次时间 |
| 机器 ID 分配 | Zookeeper / 配置中心 |
| 序列号耗尽 | 等到下一毫秒 |
Snowflake 代码示例(精简版)
public class SnowflakeIdGenerator {
private final long workerId;
private final long epoch = 1700000000000L;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & 4095;
if (sequence == 0) {
timestamp = waitNextMillis(timestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - epoch) << 22)
| (workerId << 12)
| sequence;
}
private long waitNextMillis(long timestamp) {
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
6️⃣ 号段模式增强版:Leaf / TinyID(大厂方案)
核心思想
DB + 缓存 + 双 Buffer
- 一个号段快用完时
- 异步加载下一个号段
- 无阻塞切换
✅ 美团 Leaf、滴滴 TinyID 都是这个思路
👉 适合高并发、核心业务
四、方案选型建议(面试总结用)
| 场景 | 推荐方案 |
|---|---|
| 单体系统 | 数据库自增 |
| 中小规模 | 数据库号段 |
| 高并发核心 | Snowflake |
| 大厂级 | Leaf / TinyID |
| 非核心业务 | Redis / UUID |
五、面试 5 分钟口述版(直接背)
面试官:如何设计分布式 ID?
参考回答:
分布式 ID 的核心目标是全局唯一、趋势递增、高性能和高可用。常见方案有 UUID、数据库自增、数据库号段、Redis 自增和 Snowflake。
UUID 虽然简单,但无序且太长,不适合作为数据库主键;数据库自增在分库分表场景下会成为瓶颈;数据库号段模式通过批量取号、内存发号,性能和可用性都不错,适合中小规模系统;Redis 自增依赖 Redis 的可用性,适合非核心业务。
目前最主流的是 Snowflake 算法,它利用时间戳、机器 ID 和序列号生成 64 位 Long 型 ID,趋势递增、本地生成、性能非常高,是 Twitter 开源的工业级方案。实际使用时会遇到时钟回拨问题,可以通过等待或异常方式处理,机器 ID 可以通过 Zookeeper 或配置中心分配。
在大规模场景下,像美团 Leaf、滴滴 TinyID 这样的号段增强方案,通过双 Buffer 预加载,进一步提升了稳定性和性能。
实际选型时,我会根据业务规模和一致性要求来决定,核心业务优先 Snowflake 或 Leaf,非核心业务可以考虑 Redis 或号段模式。
六、一句话总结(收尾金句)
"分布式 ID 没有万能方案,核心是在唯一性、性能和可维护性之间做权衡,Snowflake 是目前最成熟、最通用的选择。"
七、面试官常追问 & 标准答法
| 追问 | 回答 |
|---|---|
| Snowflake 时钟回拨怎么办? | 等待 / 抛异常 / 使用上次时间 |
| 机器 ID 怎么分配? | Zookeeper / 配置中心 / 启动时分配 |
| 为什么 ID 要趋势递增? | 减少 MySQL 页分裂 |
| 号段模式浪费 ID 怎么办? | 可接受,用可维护性换性能 |
| 分布式 ID 能包含业务信息吗? | 可以,但不建议,ID 应解耦业务 |