Java ID 生成策略全面解析:从单机到分布式最佳实践
Java ID 生成方案涵盖数据库自增、UUID、雪花算法、Redis 自增、号段模式及组合策略。数据库自增简单但分库分表困难;UUID 全局唯一但无序且占用空间大;雪花算法趋势递增高性能但需处理时钟回拨;Redis 自增依赖中间件;号段模式性能极高但 ID 不连续;组合策略可读性强。高并发场景推荐号段或雪花,已有 Redis 可用 Redis 自增,需业务可读性选组合策略。

Java ID 生成方案涵盖数据库自增、UUID、雪花算法、Redis 自增、号段模式及组合策略。数据库自增简单但分库分表困难;UUID 全局唯一但无序且占用空间大;雪花算法趋势递增高性能但需处理时钟回拨;Redis 自增依赖中间件;号段模式性能极高但 ID 不连续;组合策略可读性强。高并发场景推荐号段或雪花,已有 Redis 可用 Redis 自增,需业务可读性选组合策略。

在 Java 应用开发中,ID 生成是一个看似简单却至关重要的基础组件。不同的业务场景、系统架构对 ID 生成策略有着截然不同的要求。本文将系统梳理 Java 中常见的 ID 生成方案,从单机到分布式,从简单到复杂,帮助你在实际项目中做出最合适的技术选型。
数据库自增 ID 是最传统也是最简单的 ID 生成方式,通过数据库的 AUTO_INCREMENT(MySQL)或 SEQUENCE(Oracle/PostgreSQL)机制实现。
CREATE TABLE user ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL );
CREATE SEQUENCE user_id_seq START 1 INCREMENT 1;
CREATE TABLE user ( id BIGINT DEFAULT nextval('user_id_seq') PRIMARY KEY, name VARCHAR(50) NOT NULL );
优点:
缺点:
UUID 是一个 128 位的全局唯一标识符,标准格式包含 32 个十六进制数字,以连字符分隔的五组形式显示,例如:550e8400-e29b-41d4-a716-446655440000。
Java 实现:
import java.util.UUID;
public class UUIDGenerator {
public static String generate() {
return UUID.randomUUID().toString();
}
// 不带连字符的版本
public static String generateWithoutHyphens() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
优点:
缺点:
雪花算法是 Twitter 开源的分布式 ID 生成算法,将 64 位 ID 划分为多个部分: 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 └───────────────────────────────────────────────────────────────────────────────┘ 1 位符号位(始终为 0)41 位时间戳(毫秒级)5 位数据中心 ID 5 位机器 ID 12 位序列号
Java 实现示例:
public class SnowflakeIdGenerator {
// 起始时间戳(2020-01-01)
private static final long START_TIMESTAMP = 1577808000000L;
// 机器 ID 占用的位数
private static final long MACHINE_BIT = 5L;
// 数据中心 ID 占用的位数
private static final long DATACENTER_BIT = 5L;
// 序列号占用的位数
private static final long SEQUENCE_BIT = 12L;
// 最大机器 ID
private static final long MAX_MACHINE_ID = -1L ^ (-1L << MACHINE_BIT);
// 最大数据中心 ID
private static final long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_BIT);
// 序列号掩码
private - ^ (- << SEQUENCE_BIT);
SEQUENCE_BIT + MACHINE_BIT + DATACENTER_BIT;
SEQUENCE_BIT + MACHINE_BIT;
SEQUENCE_BIT;
datacenterId;
machineId;
;
-;
{
(datacenterId > MAX_DATACENTER_ID || datacenterId < ) {
( + MAX_DATACENTER_ID);
}
(machineId > MAX_MACHINE_ID || machineId < ) {
( + MAX_MACHINE_ID);
}
.datacenterId = datacenterId;
.machineId = machineId;
}
{
System.currentTimeMillis();
(currentTimestamp < lastTimestamp) {
();
}
(currentTimestamp == lastTimestamp) {
sequence = (sequence + ) & SEQUENCE_MASK;
(sequence == ) {
currentTimestamp = waitNextMillis(lastTimestamp);
}
} {
sequence = ;
}
lastTimestamp = currentTimestamp;
((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) | (datacenterId << DATACENTER_LEFT_SHIFT) | (machineId << MACHINE_LEFT_SHIFT) | sequence;
}
{
System.currentTimeMillis();
(timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
timestamp;
}
}
时钟回拨是雪花算法的主要挑战,解决方案包括:
private long waitNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
try { Thread.sleep(1); } catch (InterruptedException e) {}
timestamp = System.currentTimeMillis();
}
return timestamp;
}
public class SnowflakeIdGeneratorWithBackup {
private SnowflakeIdGenerator primary;
private SnowflakeIdGenerator backup;
public long nextId() {
try {
return primary.nextId();
} catch (RuntimeException e) {
return backup.nextId();
}
}
}
优点:
缺点:
利用 Redis 的原子操作 INCR 或 INCRBY 实现 ID 自增。
基础实现:
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private Jedis jedis;
private String key;
public RedisIdGenerator(String host, int port, String key) {
this.jedis = new Jedis(host, port);
this.key = key;
}
public long nextId() {
return jedis.incr(key);
}
// 批量获取 ID 段
public long[] nextIds(int batchSize) {
long end = jedis.incrBy(key, batchSize);
long start = end - batchSize + 1;
long[] ids = new long[batchSize];
for (int i = 0; i < batchSize; i++) {
ids[i] = start + i;
}
return ids;
}
}
Redis Cluster 实现:
import redis.clients.jedis.JedisCluster;
public class RedisClusterIdGenerator {
private JedisCluster jedisCluster;
private String key;
public RedisClusterIdGenerator(JedisCluster jedisCluster, String key) {
this.jedisCluster = jedisCluster;
this.key = key;
}
public long nextId() {
return jedisCluster.incr(key);
}
}
优点:
缺点:
从数据库批量获取 ID 段,缓存在本地内存中,用完后再次获取。
数据库表设计:
CREATE TABLE id_generator (
biz_tag VARCHAR(50) PRIMARY KEY COMMENT '业务标识',
max_id BIGINT NOT NULL DEFAULT 0 COMMENT '当前最大 ID',
step INT NOT NULL DEFAULT 1000 COMMENT '号段步长',
version BIGINT NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁)',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Java 实现:
@Component
public class SegmentIdGenerator {
@Autowired
private JdbcTemplate jdbcTemplate;
private Map<String, Segment> segmentCache = new ConcurrentHashMap<>();
public long nextId(String bizTag) {
Segment segment = segmentCache.get(bizTag);
if (segment == null || segment.isExhausted()) {
segment = updateSegmentFromDb(bizTag);
segmentCache.put(bizTag, segment);
}
return segment.nextId();
}
private Segment updateSegmentFromDb(String bizTag) {
String sql = "UPDATE id_generator SET max_id = max_id + step, version = version + 1 WHERE biz_tag = ? AND version = ?";
int affectedRows = jdbcTemplate.update(sql, bizTag, getCurrentVersion(bizTag));
if (affectedRows == 0) {
throw new RuntimeException("更新号段失败,请重试");
}
String querySql = "SELECT max_id, step FROM id_generator WHERE biz_tag = ?";
return jdbcTemplate.queryForObject(querySql, (rs, rowNum) -> {
long maxId = rs.getLong("max_id");
int rs.getInt();
(maxId - step, maxId);
}, bizTag);
}
{
;
jdbcTemplate.queryForObject(sql, Long.class, bizTag);
}
{
current;
end;
{
.current = start;
.end = end;
}
{
(current >= end) {
();
}
current++;
}
{
current >= end;
}
}
}
为了平滑获取号段的性能抖动,可以采用双 Buffer 机制:
public class DoubleBufferSegmentIdGenerator {
private Segment currentSegment;
private Segment nextSegment;
private volatile boolean loadingNext = false;
public synchronized long nextId(String bizTag) {
if (currentSegment == null || currentSegment.isExhausted()) {
if (nextSegment != null) {
currentSegment = nextSegment;
nextSegment = null;
loadingNext = false;
} else {
currentSegment = updateSegmentFromDb(bizTag);
}
}
// 异步加载下一个号段
if (nextSegment == null && !loadingNext && currentSegment.getRemaining() < threshold) {
loadingNext = true;
CompletableFuture.runAsync(() -> {
nextSegment = updateSegmentFromDb(bizTag);
loadingNext = false;
});
}
return currentSegment.nextId();
}
}
优点:
缺点:
将业务标识、时间戳、序列号等组合生成可读性强的 ID。
示例:订单 ID 生成
public class OrderIdGenerator {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final AtomicLong SEQUENCE = new AtomicLong(0);
private static final int MAX_SEQUENCE = 9999;
public static String generate() {
String date = LocalDate.now().format(DATE_FORMATTER);
long sequence = SEQUENCE.incrementAndGet() % (MAX_SEQUENCE + 1);
return "ORD-" + date + "-" + String.format("%04d", sequence);
}
}
// 输出示例:ORD-20250109-0001
public class DistributedBizIdGenerator {
private static final String MACHINE_ID = System.getenv("MACHINE_ID"); // 机器标识
private static final AtomicLong SEQUENCE = new AtomicLong(0);
public static String generate(String bizPrefix) {
String timestamp = String.valueOf(System.currentTimeMillis());
long sequence = SEQUENCE.incrementAndGet();
return bizPrefix + "-" + MACHINE_ID + "-" + timestamp + "-" + sequence;
}
}
优点:
缺点:
| 方案 | 性能 | 分布式 | 连续性 | 可读性 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|---|
| 数据库自增 | 中 | 否 | 连续 | 差 | 低 | 单机小系统 |
| UUID | 高 | 是 | 无序 | 差 | 低 | 分布式临时数据 |
| 雪花算法 | 极高 | 是 | 趋势递增 | 差 | 中 | 高并发分布式 |
| Redis 自增 | 高 | 是 | 连续 | 差 | 中 | 有 Redis 集群 |
| 号段模式 | 极高 | 是 | 分段连续 | 差 | 高 | 超高并发系统 |
| 组合策略 | 高 | 是 | 无序 | 好 | 中 | 需要业务可读性 |
采用雪花算法 + 业务前缀的组合方案:
public class OrderIdGenerator {
private static SnowflakeIdGenerator snowflake = new SnowflakeIdGenerator(1, 1);
public static String generate(String orderType) {
long id = snowflake.nextId();
return orderType + "-" + id;
}
}
// ID 示例:NORMAL-1234567890123456789
# application.yml
snowflake:
datacenter-id: ${DATACENTER_ID:1}
machine-id: ${MACHINE_ID:1}
@Component
public class SnowflakeHealthCheck implements HealthIndicator {
@Autowired
private SnowflakeIdGenerator snowflake;
@Override
public Health health() {
try {
snowflake.nextId();
return Health.up().build();
} catch (Exception e) {
return Health.down().withDetail("error", e.getMessage()).build();
}
}
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online