基于雪花算法的分布式唯一 ID 生成器实现
在分布式系统中,生成全局唯一的 ID 是一项基础且关键的任务。雪花算法(Snowflake)由 Twitter 开源,其生成的 ID 是一个 64 位的长整型(Long),具有趋势递增、高效、不依赖数据库等优点。本文将介绍如何在 Java 环境中实现该算法,并结合 Spring 框架进行配置。
1. 核心算法实现
雪花算法生成的 64 位 ID 通常分为以下几部分:
- 1位符号位:始终为 0,确保 ID 为正数。
- 41位时间戳:记录毫秒级时间,可支撑约 69 年。
- 10位工作机器 ID:通常分为 5 位数据中心 ID 和 5 位工作节点 ID。
- 12位序列号:同一毫秒内产生的不同 ID,每毫秒支持生成 4096 个。
以下是改进后的 Java 实现代码,通过位运算拼接各部分,并增加了时钟回退校验:
import org.activiti.engine.impl.cfg.IdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CustomSnowflakeGenerator implements IdGenerator {
private static final Logger logger = LoggerFactory.getLogger(CustomSnowflakeGenerator.class);
// 起始时间戳 (2020-01-01)
private final long epoch = 1577836800000L;
// 各部分占用的位数
private final long nodeIdBits = 5L;
private final long dataCenterIdBits = 5L;
private final long sequenceBits = 12L;
// 各部分最大值
private final long maxNodeId = -1L ^ (-1L << nodeIdBits);
private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
// 位移偏移量
private final long nodeIdShift = sequenceBits;
private final long dataCenterIdShift = sequenceBits + nodeIdBits;
private final long timestampLeftShift = sequenceBits + nodeIdBits + dataCenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long nodeId;
private long dataCenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public CustomSnowflakeGenerator(long nodeId, long dataCenterId) {
if (nodeId > maxNodeId || nodeId < 0) {
throw new IllegalArgumentException(String.format("Node ID 必须在 0 到 %d 之间", maxNodeId));
}
if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
throw new IllegalArgumentException(String.format("DataCenter ID 必须在 0 到 %d 之间", maxDataCenterId));
}
this.nodeId = nodeId;
this.dataCenterId = dataCenterId;
}
/**
* 生成下一个唯一的 ID (线程安全)
*/
public synchronized long generateLongId() {
long currentTimestamp = getCurrentTime();
// 检查系统时钟回退
if (currentTimestamp < lastTimestamp) {
long offset = lastTimestamp - currentTimestamp;
logger.error("系统时钟回退,拒绝生成 ID,偏移量为 {} 毫秒", offset);
throw new RuntimeException("Clock moved backwards.");
}
if (lastTimestamp == currentTimestamp) {
// 同一毫秒内,累加序列
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 序列溢出,等待下一毫秒
currentTimestamp = blockUntilNextMillis(lastTimestamp);
}
} else {
// 时间戳改变,重置序列
sequence = 0L;
}
lastTimestamp = currentTimestamp;
// 位运算组合成 64 位 ID
return ((currentTimestamp - epoch) << timestampLeftShift)
| (dataCenterId << dataCenterIdShift)
| (nodeId << nodeIdShift)
| sequence;
}
private long blockUntilNextMillis(long lastTimestamp) {
long timestamp = getCurrentTime();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentTime();
}
return timestamp;
}
private long getCurrentTime() {
return System.currentTimeMillis();
}
/**
* 实现 Activiti 的 IdGenerator 接口
*/
@Override
public String getNextId() {
return String.valueOf(generateLongId());
}
}
2. Spring 框架集成配置
为了在 Spring 项目中便捷地使用该生成器,可以通过 Java Config 的方式将其注册为 Bean。在配置类中,可以根据环境参数指定工作机器 ID 和数据中心 ID。
import org.activiti.engine.impl.cfg.IdGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SnowflakeIdentifierConfig {
/**
* 注册 ID 生成器 Bean
* 实际生产环境中,nodeId 和 dataCenterId 建议通过配置文件或环境变量获取
*/
@Bean
public IdGenerator idGenerator() {
// 示例:节点 ID 为 1,数据中心 ID 为 1
return new CustomSnowflakeGenerator(1, 1);
}
}
3. 算法优势总结
通过这种方式实现的 ID 生成策略具有以下显著特点:
- 高性能:本地生成,不涉及网络 IO 消耗。
- 结构清晰:ID 包含时间信息,便于按时间排序和数据分片。
- 扩展性:通过调整工作机器 ID 的位数,可以支持更多的物理节点。
- 兼容性:通过实现
IdGenerator接口,可以无缝对接 Activiti 等需要自定义 ID 生成逻辑的工作流引擎。
