外观
一句话答案
常用方案:雪花算法(有序高性能)、号段模式(预分配+高可用)、Redis INCR(简单有序)、UUID(无序不推荐做主键)。
核心要点
为什么需要分布式 ID? 在分库分表或微服务架构下,数据库自增 ID 无法保证全局唯一,需要一种全局唯一的 ID 生成方案。
常见方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| UUID | 本地生成,无网络开销,全球唯一 | 无序(B+ 树插入性能差)、太长(128 位字符串)、不适合做主键 |
| 数据库自增 | 简单、有序 | 单点瓶颈、分库后不唯一、性能受限于 DB |
| 数据库号段模式 | 批量获取减少 DB 访问 | 仍依赖 DB、号段用完需重新申请 |
| Redis INCR | 性能高、有序 | 依赖 Redis 可用性、持久化问题 |
| Snowflake 雪花算法 | 本地生成、有序、高性能 | 依赖机器时钟、时钟回拨问题 |
Snowflake 雪花算法原理(64 位 Long 型 ID):
0 | 0000000 00000000 00000000 00000000 00000000 0 | 00000 00000 | 000000000000
│ │ │ │
│ │ 41 位时间戳 │ 10 位机器ID │ 12 位序列号
│ │ (毫秒级,可用约 69 年) │ │
│ │ │ │
│ 1 位符号位(固定为 0,表示正数) │ │
│ 5位数据中心 + 5位机器
│ (最多 1024 个节点)
│
最高位(始终为 0)
各部分的含义:
1 位:符号位,固定 0
41 位:时间戳(当前时间 - 起始时间的毫秒差),可用约 69 年
10 位:工作机器 ID(5 位 datacenterId + 5 位 workerId),最多 1024 台机器
12 位:同一毫秒内的序列号,最多 4096 个/毫秒
单机理论 QPS:4096 * 1000 = 409.6 万/秒Snowflake 的时钟回拨问题:
问题:服务器时钟被回调(NTP 同步、手动修改),导致生成重复 ID
解决方案:
① 直接抛异常(简单粗暴)
if (currentTimestamp < lastTimestamp) throw new RuntimeException("时钟回拨");
② 等待时钟追上(适合小幅回拨,如几毫秒)
while (currentTimestamp < lastTimestamp) {
currentTimestamp = System.currentTimeMillis();
}
③ 使用扩展位(预留回拨序号位)业界改进方案:
| 方案 | 团队 | 改进点 |
|---|---|---|
| Leaf(美团) | 美团点评 | 号段模式 + Snowflake 双模式,号段模式预加载双 buffer |
| uid-generator(百度) | 百度 | 借鉴 Snowflake,使用 RingBuffer 预生成 ID 缓存,解决时钟回拨 |
| Tinyid(滴滴) | 滴滴 | 号段模式 + 多 DB 支持 |
追问与易错
追问方向:
- 你们用的哪种方案?
- Leaf 和 UidGenerator 了解吗?
- 需要全局递增还是趋势递增?
易错点:
- ❌ UUID 也能做分布式 ID——无序不适合做主键
- ❌ 雪花算法不会出错——时钟回拨必须处理
💡 记忆锚点
分布式ID四大门派:UUID本地快但无序(B+树噩梦)、Redis INCR有序但多一层依赖、号段模式批量取号减少DB压力、雪花算法本地生成64位有序ID(1位符号+41位时间戳+10位机器+12位序列=单机400万/秒)。雪花的命门是时钟回拨,必须防。