分布式 ID 完整选型 + 落地方案(生产可用,分场景直接选)
一、主流 5 种方案优缺点总览
1. UUID / UUID36
- 优点:本地生成,无 RPC、简单
- 缺点:无序、太长 36 位、数据库索引差、泄露 MAC 地址(旧版)
- 适用:简单日志、临时业务 ID,严禁做主键
2. 数据库自增主键(单库)
- 优点:连续有序、稳定、最简单
- 缺点:单点瓶颈,分库分表完全不能用
- 适用:单体小型系统
3. 数据库号段模式(Leaf、百度 UidGenerator)
原理:业务一次性批量拿一段 ID 缓存本地,用完再去 DB 申请下一号段
- 优点:有序、性能极高、稳定、可持久化、故障不重复
- 缺点:需要单独一张号段表,轻微开发量
- 适用:绝大多数企业业务、订单、财务、主业务主键(最稳妥通用选型)
4. Redis INCR / 号段
- 优点:性能爆炸,原子自增
- 缺点:宕机丢未持久化增量、依赖 Redis 集群稳定性
- 适用:高并发秒杀、临时流水号
5. Snowflake 雪花算法(Twitter)
64bit 结构:时间戳 (41)+ 机器 ID (10)+ 序列号 (12)
- 优点:全局有序、纯本地生成、不依赖第三方、64 位 long 型
- 缺点:时钟回拨致命问题、机器 ID 要手动分配维护
- 适用:高并发互联网业务、海量数据场景;必须解决时钟回拨
6. 美团 Leaf(同时支持雪花 + 号段)
企业级成熟开源框架,直接拿来用,业界落地最多。
二、明确选型决策表(直接对照业务选)
场景 1:传统 ERP/MES/ 财务 / 支付、要求绝对不重复、数据稳妥
✅优先:Leaf 号段模式
- 强一致性、无时钟问题、运维简单、数据库可溯源
- 分库分表天然支持
场景 2:超高并发、百万 QPS、海量流水、不在乎短暂时钟风险
✅Snowflake(自研 / Leaf 雪花模式)必须配套:时钟监控、回拨拒绝策略、机器 ID 注册中心
场景 3:简单临时 ID、内部埋点、非主键
✅ UUID / 简单雪花
场景 4:秒杀短时效流水号
✅ Redis 自增
三、雪花算法详细落地关键点(避坑核心)
64 位标准结构
1bit 符号位 | 41bit 毫秒时间戳 | 10bit 机器 ID | 12bit 序列号
- 机器 ID 10bit:最多 1024 台机器
- 序列号 12bit:每毫秒单节点最多 4096 个 ID
三大致命坑 & 解决方案
- 时钟回拨(最严重)
- 现象:服务器 NTP 同步回拨,生成重复 ID
- 解决:
- 本机记录上一次生成时间,当前时间 < 上次时间 → 直接抛出异常 / 自旋等待
- Leaf 雪花自带时间回拨补偿
- 禁止虚拟机时间漂移
- 机器 ID 重复
- 方案:
- 启动注册到 Zookeeper/Redis,自动分配唯一机器号
- 物理机用 IP 最后 10 位、容器用 K8s pod 固定标识
- 单机毫秒内 4096 序列号耗尽
- 自旋等到下一毫秒即可
四、号段模式落地架构(强烈推荐制造业 / 企业系统)
1. 单独号段数据表
CREATE TABLE id_segment ( business_tag VARCHAR(50) PRIMARY KEY COMMENT '业务标识:order,receivable', max_id BIGINT NOT NULL COMMENT '当前已分配最大号', step INT NOT NULL COMMENT '每次申请步长', update_time DATETIME );示例初始化一条:order,max_id=10000,step=1000
2. 运行逻辑
- 服务启动拉取一段:10001~11000 缓存在内存
- 业务直接内存取号,无 DB 请求
- 内存剩余 10% 阈值,异步去 DB 抢占下一段号段(CAS 更新 max_id)
- 数据库只有简单 UPDATE:
update id_segment set max_id = max_id + step where business_tag = ?
- 原子更新,绝对不会重复
3. 优势
- DB 宕机,内存号段还能继续支撑很久
- 完全没有时钟问题,金融财务零风险
- 有序数字 ID,MySQL 主键索引性能拉满
五、开源成熟框架直接落地(不用重复造轮子)
- 美团 Leaf(首选)两种模式自由切换:Segment 号段 + Snowflake 支持监控、号段自动扩容、雪花时钟回拨处理、RPC 接入简单
- 百度 UidGenerator:基于雪花,借助 Redis 解决机器 ID 与时钟
- 滴滴 TinyId:纯号段方案,轻量
六、Java 生产极简落地建议
- 企业内部管理系统、MES、WMS、财务单据采用:Leaf Segment 号段
- 高并发 C 端业务、千万级订单采用:Leaf Snowflake 模式,开启时钟回拨保护
- 禁止自己手写简易雪花,时钟回拨一定会踩坑
七、分库分表配套规则
- 雪花 ID:取 ID 取模分片
- 号段 ID:同样取模,全局连续完全兼容分片
三套完整可直接投产代码:自研号段工具类 + 安全雪花算法 + SpringBoot 接入美团 Leaf
一、自研数据库号段分布式 ID(企业 ERP/MES/ 财务最稳)
1、号段数据表
CREATE TABLE `id_segment` ( `business_tag` varchar(50) NOT NULL COMMENT '业务标识:order、receivable、work_order', `max_id` bigint NOT NULL DEFAULT 0 COMMENT '已分配最大ID', `step` int NOT NULL DEFAULT 1000 COMMENT '每次申请号段长度', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`business_tag`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式号段表'; -- 初始化测试数据 INSERT INTO id_segment(business_tag,max_id,step) VALUES ('order',10000,2000);2、MyBatis Mapper
@Mapper public interface IdSegmentMapper { @Update("UPDATE id_segment SET max_id = max_id + step WHERE business_tag = #{tag}") int increaseSegment(@Param("tag") String tag); @Select("SELECT max_id,step FROM id_segment WHERE business_tag = #{tag}") IdSegment selectByTag(@Param("tag") String tag); } @Data public class IdSegment { private Long maxId; private Integer step; }3、号段生成器核心工具类
import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; @Component public class SegmentIdGenerator { @Resource private IdSegmentMapper idSegmentMapper; // 本地缓存号段 private final Map<String, SegmentBuffer> bufferMap = new ConcurrentHashMap<>(); // 异步预加载线程池 private final ExecutorService pool = Executors.newSingleThreadExecutor(); private static class SegmentBuffer { // 当前号段起始、截止、当前取值 AtomicLong current; long end; int step; volatile boolean isLoading; } public Long nextId(String businessTag) { SegmentBuffer buffer = bufferMap.computeIfAbsent(businessTag, k -> initBuffer(k)); long id = buffer.current.getAndIncrement(); // 剩余10%阈值,异步拉取下一号段 if (id >= buffer.end - buffer.step * 0.1 && !buffer.isLoading) { loadNextAsync(buffer, businessTag); } if (id > buffer.end) { synchronized (buffer) { while (id > buffer.end) { try { buffer.wait(); } catch (InterruptedException e) { throw new RuntimeException("获取号段超时"); } } id = buffer.current.getAndIncrement(); } } return id; } private SegmentBuffer initBuffer(String tag) { IdSegment seg = idSegmentMapper.selectByTag(tag); SegmentBuffer buf = new SegmentBuffer(); buf.step = seg.getStep(); buf.end = seg.getMaxId(); buf.current = new AtomicLong(buf.end - buf.step + 1); return buf; } private void loadNextAsync(SegmentBuffer buffer, String tag) { if (!buffer.isLoading) { buffer.isLoading = true; pool.submit(() -> { try { // CAS原子递增 idSegmentMapper.increaseSegment(tag); IdSegment newSeg = idSegmentMapper.selectByTag(tag); synchronized (buffer) { buffer.end = newSeg.getMaxId(); buffer.current.set(buffer.end - buffer.step + 1); buffer.notifyAll(); } } finally { buffer.isLoading = false; } }); } } }使用
@Autowired private SegmentIdGenerator segmentIdGenerator; Long orderId = segmentIdGenerator.nextId("order");二、自带时钟回拨防护的雪花算法(可直接上线)
public class SafeSnowflakeIdGenerator { // 64位划分 private static final long TIME_BIT = 41L; private static final long WORKER_BIT = 10L; private static final long SEQ_BIT = 12L; private static final long MAX_WORKER_ID = (1L << WORKER_BIT) - 1; private static final long MAX_SEQ = (1L << SEQ_BIT) - 1; private static final long WORKER_SHIFT = SEQ_BIT; private static final long TIME_SHIFT = SEQ_BIT + WORKER_BIT; // 基准时间:2025-01-01 private static final long EPOCH = 1735689600000L; private final long workerId; private long lastTime = 0L; private long sequence = 0L; public SafeSnowflakeIdGenerator(long workerId) { if (workerId < 0 || workerId > MAX_WORKER_ID) { throw new IllegalArgumentException("机器ID超出0~1023"); } this.workerId = workerId; } public synchronized long nextId() { long now = System.currentTimeMillis(); // 时钟回拨 if (now < lastTime) { long offset = lastTime - now; // 回拨小于50ms自旋等待,超过直接抛异常拒绝生成 if (offset < 50) { try { Thread.sleep(offset); now = System.currentTimeMillis(); } catch (InterruptedException e) { throw new RuntimeException("时钟回拨,生成ID失败"); } } else { throw new RuntimeException("严重时钟回拨,禁止生成ID"); } } // 同一毫秒自增序列号 if (now == lastTime) { sequence = (sequence + 1) & MAX_SEQ; if (sequence == 0) { // 序列号用尽,等到下一毫秒 while (now <= lastTime) { now = System.currentTimeMillis(); } } } else { sequence = 0; } lastTime = now; return ((now - EPOCH) << TIME_SHIFT) | (workerId << WORKER_SHIFT) | sequence; } }机器 ID 分配方案
- 物理机:
IP最后两段取模1024 - K8s 容器:注册到 Redis/ZK 自动分配唯一 workerId
三、SpringBoot 接入美团 Leaf 完整配置(生产首选)
1、架构选择
同时部署Segment 号段做主业务 ID,Snowflake 做高并发流水 ID
2、application.yml
leaf: segment: enable: true url: jdbc:mysql://localhost:3306/leaf_db?useUnicode=true username: root password: xxx snowflake: enable: true # 使用ZK自动分配workerId,杜绝重复 zk-address: 127.0.0.1:21813、依赖 Maven
<dependency> <groupId>com.sankuai.inf.leaf</groupId> <artifactId>leaf-core</artifactId> <version>1.0.4</version> </dependency>4、注入使用
@Autowired private SegmentIDGenImpl segmentIDGen; @Autowired private SnowflakeIDGenImpl snowflakeIDGen; // 号段ID(订单、财务单据) Long orderId = segmentIDGen.get("order").getId(); // 雪花ID(高并发流水) Long flowId = snowflakeIDGen.get("flow").getId();四、三种方案选型极简总结
- MES/WMS/ 财务 / ERP 单据、要求绝对稳定无时钟风险→ 自研号段 / Leaf Segment
- 高并发千万级 C 端订单、海量数据→ 安全雪花 / Leaf 雪花
- 小型单体项目→ 简单雪花即可