本文目录导读:

- 方案一:基于 UUID(简单但无序)
- 方案二:基于数据库自增 ID(经典但依赖 DB)🚀
- 方案三:基于 Redis INCR 命令(性能好,适合中小应用)
- 方案四:雪花算法(Snowflake,最主流推荐)
- 方案五:美团 Leaf(工业级方案)
- 总结:如何选择?
Java 分布式 ID 的生成是分布式系统设计中的一个经典问题,主要需要解决 全局唯一性、趋势递增(便于数据库索引)、高可用 和 高性能。
以下是几种主流的 Java 分布式 ID 生成方案及案例代码,从简单到复杂依次介绍:
基于 UUID(简单但无序)
UUID 是 Java 内置的,生成简单,但缺点明显:无序、过长、无业务含义,不适合做数据库主键(B+树索引维护代价高)。
import java.util.UUID;
public class UUIDGenerator {
// 生成无横线的32位字符串
public static String generate() {
return UUID.randomUUID().toString().replace("-", "");
}
public static void main(String[] args) {
System.out.println(generate());
// 输出类似:a1b2c3d4e5f6789012345678abcdef01
}
}
基于数据库自增 ID(经典但依赖 DB)🚀
利用数据库的 auto_increment 或 sequence,适用于小规模或对性能要求不高的场景。
1 单库步长模式(避免每次查询)
-- MySQL 示例:设置步长 10,起始值
-- 每台机器配置不同的起始值,避免冲突
CREATE TABLE id_generator (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
stub CHAR(1) NOT NULL DEFAULT '' UNIQUE KEY
);
-- 每次插入获取一个区间
REPLACE INTO id_generator (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
2 号段模式(Leaf 方案)
这是最常见的数据库优化方案:一次获取一段 ID(如 1000 个),在内存中分发,避免了每次请求都访问数据库。
public class SegmentIdGenerator {
private static final int STEP = 1000; // 步长
private long currentId; // 当前ID
private long maxId; // 当前区段上限
private DataSource dataSource;
public SegmentIdGenerator(DataSource dataSource) {
this.dataSource = dataSource;
this.currentId = 0;
this.maxId = 0;
}
// 核心方法:获取下一个ID
public synchronized long nextId() {
if (currentId >= maxId) {
// 当前区间用完,申请新区间
Segment segment = fetchNewSegment();
currentId = segment.getStartId();
maxId = segment.getMaxId();
}
return currentId++;
}
// 查询数据库,更新并获取新的步长区间
private Segment fetchNewSegment() {
// 这里省略 JDBC 具体连接代码,核心 SQL 如下:
// UPDATE id_table SET max_id = max_id + ? WHERE business = 'order';
// 获取更新后的 max_id
long newMaxId = getUpdatedMaxIdFromDB();
return new Segment(newMaxId - STEP, newMaxId);
}
static class Segment {
private long startId;
private long maxId;
// 构造省略...
}
}
优点:实现简单,可管理。
缺点:依赖 DB 性能,需要高可用。
基于 Redis INCR 命令(性能好,适合中小应用)
利用 Redis 的单线程特性保证原子唯一性。
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private Jedis jedis;
private static final String KEY = "id_generator:order";
public RedisIdGenerator(Jedis jedis) {
this.jedis = jedis;
}
public long nextId() {
// 自增并返回,原子操作
return jedis.incr(KEY);
}
/**
* 如果你想生成 时间戳+序列号 的ID(带趋势递增)
* @param keyPrefix 业务前缀
* @return 拼接后的ID
*/
public long generateTimeBasedId(String keyPrefix) {
long timestamp = System.currentTimeMillis();
// 每天一个key,避免无限增长
String dailyKey = keyPrefix + ":" + timestamp / (1000 * 60 * 60 * 24);
long sequence = jedis.incr(dailyKey);
// 组合:时间戳占高位,序列号占低位
return (timestamp << 12) | (sequence & 0xFFF);
}
public static void main(String[] args) {
RedisIdGenerator generator = new RedisIdGenerator(new Jedis("localhost"));
for (int i = 0; i < 5; i++) {
System.out.println(generator.generateTimeBasedId("order"));
}
}
}
优点:性能高(单机10万 QPS)。
缺点:依赖 Redis 高可用,Redis 崩溃且没持久化 ID可能重复。
雪花算法(Snowflake,最主流推荐)
这是 Twitter 开源的算法,java 实现广泛,ID 由 时间戳 + 机器ID + 序列号 组成,趋势递增,不依赖第三方中间件。
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L; // 起始时间戳,可自定义
private final long workerIdBits = 5L; // 机器ID位数
private final long datacenterIdBits = 5L; // 数据中心ID位数
private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 最大31
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 最大31
private final long sequenceBits = 12L; // 序列号位数
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 4095
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
// 构造函数:初始化机器ID和数据中心ID(从配置文件/环境变量读取)
public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("workerId 非法");
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId 非法");
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
// 时钟回拨处理(常见bug点)
if (timestamp < lastTimestamp) {
// 可以抛异常,也可以等待时钟追上,这里简化处理
throw new RuntimeException("Clock moved backwards. Refusing to generate id for " +
(lastTimestamp - timestamp) + " milliseconds");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
// 如果序列号用完,等待下一个毫秒
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L; // 不同毫秒,序列重置
}
lastTimestamp = timestamp;
// 组合ID:时间戳 | 数据中心 | 机器 | 序列号
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowflakeIdGenerator idWorker = new SnowflakeIdGenerator(1, 1);
for (int i = 0; i < 5; i++) {
long id = idWorker.nextId();
System.out.println(id);
}
}
}
优点:
- 不依赖外部服务(DB、Redis),高性能(每秒可生成十几万)。
- ID 趋势递增,对 MySQL B+树索引友好。
缺点:
- 需要配置 workerId,避免机器ID冲突。
- 强依赖系统时间,如果时钟回拨,可能出现重复。
美团 Leaf(工业级方案)
Leaf 是美团开源的 ID 生成系统,集成了 雪花算法(Leaf-segment) 和 号段模式(Leaf-snowflake),它解决了雪花算法需要手动分配机器 ID、时钟回拨等问题。
用法(引入 Maven 依赖):
<dependency>
<groupId>com.sankuai.inf.leaf</groupId>
<artifactId>leaf-boot-starter</artifactId>
<version>1.0.0-RELEASE</version>
</dependency>
配置文件 application.properties:
# 号段模式 leaf.name=com.sankuai.leaf leaf.segment.enable=true leaf.segment.url=jdbc:mysql://localhost:3306/leaf leaf.segment.username=root leaf.segment.password=123456
推荐生产环境使用美团 Leaf 或百度 Uidgenerator。
如何选择?
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| UUID | 无依赖,生成快 | 无序,过长 | 不关心顺序,不适合DB主键 |
| DB自增 | 简单,有序 | 性能瓶颈,单点 | 小规模,低并发 |
| DB号段 | 性能好,可定制 | 依赖DB高可用 | 中大型应用,需更优性能 |
| Redis | 高并发,简单 | 依赖Redis,持久化问题 | 中小型应用,已有Redis |
| 雪花算法 | 高性能,不依赖第三方 | 需管理机器ID,时钟问题 | 最常见,适配合大部分场景 |
| 美团Leaf | 成熟,解决时钟回拨 | 需搭建Leaf服务 | 生产环境推荐 |
最推荐的做法:直接使用开源组件(如 Leaf 或 百度 UidGenerator),如果不想引入额外中间件,自实现雪花算法 + 通过Redis或Zookeeper分配WorkerId是最稳妥的方式。