面试知识库
极高 进阶

分布式ID生成方案#

一句话答案#

常用方案:雪花算法(有序高性能)、号段模式(预分配+高可用)、Redis INCR(简单有序)、UUID(无序不推荐做主键)。

核心要点

为什么需要分布式 ID? 在分库分表或微服务架构下,数据库自增 ID 无法保证全局唯一,需要一种全局唯一的 ID 生成方案。

常见方案对比:

方案优点缺点
UUID本地生成,无网络开销,全球唯一无序(B+ 树插入性能差)、太长(128 位,字符串形式 36 个字符)、不适合做主键
数据库自增简单、有序单点瓶颈、分库后不唯一、性能受限于 DB
数据库号段模式批量获取减少 DB 访问仍依赖 DB、号段用完需重新申请
Redis INCR性能高、有序依赖 Redis 可用性、持久化问题
Snowflake 雪花算法本地生成、有序、高性能依赖机器时钟、时钟回拨问题

Snowflake 雪花算法原理(64 位 Long 型 ID):

Snowflake 的时钟回拨问题:

问题:服务器时钟被回调(NTP 同步、手动修改),导致生成重复 ID

解决方案:
  ① 直接抛异常(简单粗暴)
     if (currentTimestamp < lastTimestamp) throw new RuntimeException("时钟回拨");

  ② 等待时钟追上(适合小幅回拨,如几毫秒)
     while (currentTimestamp < lastTimestamp) {
         currentTimestamp = System.currentTimeMillis();
     }

  ③ 使用扩展位(预留回拨序号位)
plaintext

业界改进方案:

方案团队改进点
Leaf(美团)美团点评号段模式 + Snowflake 双模式,号段模式预加载双 buffer;Snowflake 模式用 ZooKeeper 分配 workerId 并校验时钟
uid-generator(百度)百度借鉴 Snowflake(默认 28 位秒级时间 + 22 位 workerId + 13 位序列),CachedUidGenerator 用 RingBuffer 预生成 ID,时间取自增的「借用未来时间」而非每次读系统时钟,从而规避时钟回拨
Tinyid(滴滴)滴滴号段模式 + 多 DB 支持

注意(2026 年现状):Leaf 停在 1.0.1(基于 Spring Boot 1.5,未发布到 Maven Central)、uid-generator 约 2017 年后无正式发版,二者仓库都已多年无维护者活动。面试讲的是它们的设计思想;真要落地一般 fork 自维护或选仍在维护的实现。

面试回答(2分钟版)

分布式ID的核心需求是全局唯一、趋势递增、高性能。主流方案有四种。UUID本地生成无网络开销,但128位、字符串形式36个字符,太长且无序,作为数据库主键会导致B+树频繁页分裂,性能很差,一般不推荐。数据库自增ID简单有序但有单点瓶颈,分库后无法保证全局唯一。Redis INCR性能高且有序,但增加了对Redis的依赖和持久化风险。最推荐的是Snowflake雪花算法,64位Long型ID由1位符号位、41位毫秒级时间戳、10位机器ID和12位序列号组成,本地生成不依赖外部服务,单机理论QPS达到409.6万每秒,ID趋势递增对B+树索引友好。雪花算法的主要问题是时钟回拨,如果NTP同步导致时钟倒退会生成重复ID,常见解决方案是小幅回拨时等待追上,大幅回拨直接抛异常。业界改进方案有美团Leaf支持号段模式和Snowflake双模式,百度uid-generator用RingBuffer预生成ID解决时钟回拨问题。

追问与易错

追问方向:

  • “你们用的哪种方案?”→ 按场景说清选型理由:要高性能、不依赖外部服务、ID 趋势递增对 B+ 树友好 → 雪花算法(配好 workerId 分配和时钟回拨处理);要 ID 连续紧凑、机器数不固定、能接受依赖 DB → 号段模式(双 buffer 预加载);ID 不做主键、只要唯一 → UUID 即可
  • “Leaf 和 UidGenerator 了解吗?”→ Leaf 是美团开源方案支持号段+雪花双模式,号段模式双 buffer 预加载避免等待;UidGenerator 是百度方案用 RingBuffer 预生成 ID 解决时钟回拨
  • “需要全局递增还是趋势递增?”→ 大部分场景趋势递增即可(对 B+ 树索引友好),全局严格递增需要中心化生成(如 Redis INCR)有性能瓶颈,按需选择

易错点:

  • ❌ UUID 也能做分布式 ID——无序不适合做主键
  • ❌ 雪花算法不会出错——时钟回拨必须处理