Skip to content
极高进阶

一句话答案

常用方案:雪花算法(有序高性能)、号段模式(预分配+高可用)、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万/秒)。雪花的命门是时钟回拨,必须防。