分布式ID实现方式全面指南
目录
- 概述
- 分布式ID的核心需求
- UUID实现方式
- 数据库自增ID
- 数据库号段模式
- Redis实现方式
- 雪花算法(Snowflake)
- Leaf算法(美团)
- UidGenerator算法(百度)
- TinyID算法(滴滴)
- 各种实现方式对比
- 选择指南与最佳实践
- 实际应用案例
概述
在分布式系统中,生成全局唯一ID是一个核心问题。传统的单机数据库自增ID在分布式环境下会面临冲突、性能瓶颈等问题。分布式ID需要满足全局唯一性、高性能、高可用性、趋势递增等特性。
分布式ID的核心需求
基本特性
- 全局唯一性:不能出现重复的ID
- 高性能:高并发场景下生成速度快
- 高可用性:服务不能成为单点故障
- 趋势递增:有利于数据库索引性能
可选特性
- 单调递增:严格递增,有利于排序
- 信息安全:ID中不包含业务信息
- 长度可控:ID长度适中,便于存储和传输
- 可反解:能从ID中解析出有用信息
UUID实现方式
原理
UUID(Universally Unique Identifier)标准形式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符。
版本分类
- UUID1:基于时间戳和MAC地址
- UUID3:基于命名空间和MD5散列
- UUID4:基于随机数
- UUID5:基于命名空间和SHA-1散列
实现示例
import java.util.UUID;
public class UUIDGenerator {
public static String generateUUID() {
return UUID.randomUUID().toString();
}
public static String generateUUIDWithoutDash() {
return UUID.randomUUID().toString().replace("-", "");
}
}
优缺点分析
优点:
- 实现简单,无需额外依赖
- 本地生成,性能高
- 全球唯一性保证
缺点:
- 长度较长(36位或32位)
- 无序,不利于数据库索引
- 无法反解出有用信息
- 存储空间大
适用场景
- 对ID长度不敏感的场景
- 不需要排序的业务
- 临时数据标识
数据库自增ID
原理
利用数据库的自增主键特性,通过单点数据库生成唯一ID。
实现方式
-- 创建ID生成表
CREATE TABLE distributed_id (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
stub CHAR(1) NOT NULL,
UNIQUE KEY stub (stub)
) ENGINE=MyISAM;
-- 获取ID的存储过程
DELIMITER $$
CREATE PROCEDURE GetDistributedId(OUT result BIGINT)
BEGIN
INSERT INTO distributed_id (stub) VALUES ('a');
SET result = LAST_INSERT_ID();
DELETE FROM distributed_id WHERE stub = 'a';
END$$
DELIMITER ;
Java实现
@Component
public class DatabaseIdGenerator {
@Autowired
private JdbcTemplate jdbcTemplate;
public long generateId() {
String insertSql = "INSERT INTO distributed_id (stub) VALUES ('a')";
String deleteSql = "DELETE FROM distributed_id WHERE stub = 'a'";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);
return ps;
}, keyHolder);
long id = keyHolder.getKey().longValue();
jdbcTemplate.update(deleteSql);
return id;
}
}
优缺点分析
优点:
- 实现简单,易于理解
- ID单调递增,有利于数据库性能
- 数字类型,存储空间小
缺点:
- 存在单点故障风险
- 性能瓶颈,并发能力有限
- 需要数据库连接,网络开销
改进方案
- 主从架构:主库故障时切换到从库
- 双主架构:两个数据库实例同时提供服务
- 步长设置:不同实例使用不同步长
数据库号段模式
原理
一次性从数据库获取一个号段(如1000个ID),在内存中分配,用完后再获取新的号段。
数据库设计
CREATE TABLE id_generator (
id INT AUTO_INCREMENT PRIMARY KEY,
business_tag VARCHAR(50) NOT NULL COMMENT '业务标识',
max_id BIGINT NOT NULL COMMENT '当前最大ID',
step INT NOT NULL COMMENT '号段长度',
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_business_tag (business_tag)
) ENGINE=InnoDB;
实现代码
@Component
public class SegmentIdGenerator {
@Autowired
private JdbcTemplate jdbcTemplate;
private final Map<String, Segment> segments = new ConcurrentHashMap<>();
public synchronized long generateId(String businessTag) {
Segment segment = segments.get(businessTag);
if (segment == null || segment.isExhausted()) {
segment = fetchNewSegment(businessTag);
segments.put(businessTag, segment);
}
return segment.nextId();
}
private Segment fetchNewSegment(String businessTag) {
return jdbcTemplate.execute(connection -> {
connection.setAutoCommit(false);
try {
// 使用乐观锁获取号段
String selectSql = "SELECT max_id, step, version FROM id_generator WHERE business_tag = ? FOR UPDATE";
PreparedStatement selectStmt = connection.prepareStatement(selectSql);
selectStmt.setString(1, businessTag);
ResultSet rs = selectStmt.executeQuery();
if (!rs.next()) {
// 初始化业务标识
String insertSql = "INSERT INTO id_generator (business_tag, max_id, step) VALUES (?, ?, ?)";
PreparedStatement insertStmt = connection.prepareStatement(insertSql);
insertStmt.setString(1, businessTag);
insertStmt.setLong(2, 1000);
insertStmt.setInt(3, 1000);
insertStmt.executeUpdate();
connection.commit();
return new Segment(1, 1000);
}
long maxId = rs.getLong("max_id");
int step = rs.getInt("step");
int version = rs.getInt("version");
// 更新号段信息
String updateSql = "UPDATE id_generator SET max_id = ?, version = ? WHERE business_tag = ? AND version = ?";
PreparedStatement updateStmt = connection.prepareStatement(updateSql);
updateStmt.setLong(1, maxId + step);
updateStmt.setInt(2, version + 1);
updateStmt.setString(3, businessTag);
updateStmt.setInt(4, version);
int affectedRows = updateStmt.executeUpdate();
if (affectedRows == 0) {
connection.rollback();
throw new RuntimeException("Concurrent update conflict");
}
connection.commit();
return new Segment(maxId + 1, maxId + step);
} catch (Exception e) {
connection.rollback();
throw new RuntimeException("Failed to fetch segment", e);
}
});
}
private static class Segment {
private final long startId;
private final long endId;
private final AtomicLong currentId;
public Segment(long startId, long endId) {
this.startId = startId;
this.endId = endId;
this.currentId = new AtomicLong(startId);
}
public long nextId() {
long id = currentId.getAndIncrement();
if (id > endId) {
throw new RuntimeException("Segment exhausted");
}
return id;
}
public boolean isExhausted() {
return currentId.get() > endId;
}
}
}
优缺点分析
优点:
- 性能高,批量获取减少数据库访问
- 趋势递增,有利于数据库性能
- 可扩展性强,支持多业务标识
缺点:
- 存在数据库单点故障风险
- ID不连续,可能存在跳跃
- 需要处理号段用完的边界情况
Redis实现方式
原理
利用Redis的原子操作(INCR、INCRBY)生成唯一ID。
基本实现
@Component
public class RedisIdGenerator {
@Autowired
private StringRedisTemplate redisTemplate;
public long generateId(String key) {
return redisTemplate.opsForValue().increment(key);
}
public List<Long> generateIds(String key, int count) {
Long increment = redisTemplate.opsForValue().increment(key, count);
List<Long> ids = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ids.add(increment - count + i + 1);
}
return ids;
}
}
高可用方案
@Configuration
public class RedisIdConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSentinelServers()
.setMasterName("mymaster")
.addSentinelAddress("redis://sentinel1:26379", "redis://sentinel2:26379", "redis://sentinel3:26379")
.setPassword("password");
return Redisson.create(config);
}
}
@Component
public class HighAvailabilityRedisIdGenerator {
@Autowired
private RedissonClient redissonClient;
public long generateId(String businessKey) {
RAtomicLong atomicLong = redissonClient.getAtomicLong("id_generator:" + businessKey);
return atomicLong.incrementAndGet();
}
}
优缺点分析
优点:
- 性能高,基于内存操作
- 原子性保证,不会重复
- 支持批量获取
- 可扩展性好
缺点:
- 需要维护Redis集群
- 存在网络开销
- ID只是数字,不包含业务信息
- Redis故障时需要降级策略
优化策略
- 持久化配置:开启RDB和AOF持久化
- 集群部署:使用Redis Cluster或哨兵模式
- 本地缓存:结合本地号段模式
- 降级方案:Redis故障时切换到数据库
雪花算法(Snowflake)
原理
Twitter开源的分布式ID生成算法,生成64位的long型ID,结构如下:
0 | 00000000000000000000000000000000000000000 | 00000 | 00000 | 000000000000
--|-------------------------------------------|-------|-------|-------------
符号位 | 41位时间戳(毫秒) | 10位机器ID | 12位序列号
完整实现
public class SnowflakeIdGenerator {
// 起始时间戳(2020-01-01)
private static final long START_TIMESTAMP = 1577836800000L;
// 各部分位数
private static final long SEQUENCE_BITS = 12L;
private static final long WORKER_ID_BITS = 10L;
// 各部分最大值
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
// 各部分位移
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private final long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);
}
this.workerId = workerId;
}
public synchronized long generateId() {
long currentTimestamp = getCurrentTimestamp();
// 时钟回拨处理
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
}
if (currentTimestamp == lastTimestamp) {
// 同一毫秒内,序列号递增
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) {
// 序列号溢出,等待下一毫秒
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else {
// 不同毫秒,序列号重置
sequence = 0L;
}
lastTimestamp = currentTimestamp;
return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
| (workerId << WORKER_ID_SHIFT)
| sequence;
}
private long waitNextMillis(long currentTimestamp) {
while (currentTimestamp <= lastTimestamp) {
currentTimestamp = getCurrentTimestamp();
}
return currentTimestamp;
}
private long getCurrentTimestamp() {
return System.currentTimeMillis();
}
// 解析ID信息
public static class IdInfo {
private long timestamp;
private long workerId;
private long sequence;
// 构造函数和getter省略
}
public static IdInfo parseId(long id) {
IdInfo info = new IdInfo();
info.sequence = id & MAX_SEQUENCE;
info.workerId = (id >> WORKER_ID_SHIFT) & MAX_WORKER_ID;
info.timestamp = (id >> TIMESTAMP_SHIFT) + START_TIMESTAMP;
return info;
}
}
时钟回拨处理优化
public class RobustSnowflakeIdGenerator extends SnowflakeIdGenerator {
private static final long MAX_BACKWARD_MS = 10L; // 最大容忍回拨时间
public RobustSnowflakeIdGenerator(long workerId) {
super(workerId);
}
@Override
public synchronized long generateId() {
long currentTimestamp = getCurrentTimestamp();
// 时钟回拨处理
if (currentTimestamp < lastTimestamp) {
long backwardTime = lastTimestamp - currentTimestamp;
if (backwardTime <= MAX_BACKWARD_MS) {
try {
// 小幅度回拨,等待时钟追上
Thread.sleep(backwardTime);
currentTimestamp = getCurrentTimestamp();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for clock", e);
}
} else {
throw new RuntimeException("Clock moved backwards by " + backwardTime + "ms");
}
}
return super.generateId();
}
}
动态WorkerId分配
@Component
public class DynamicWorkerIdAllocator {
@Autowired
private JdbcTemplate jdbcTemplate;
private static final long MAX_WORKER_ID = 1023; // 10位机器ID最大值
public long allocateWorkerId(String applicationName, String ip, int port) {
// 尝试获取已有的workerId
String selectSql = "SELECT worker_id FROM worker_id_allocation WHERE application_name = ? AND ip = ? AND port = ?";
List<Long> existingIds = jdbcTemplate.query(selectSql,
new Object[]{applicationName, ip, port},
(rs, rowNum) -> rs.getLong("worker_id"));
if (!existingIds.isEmpty()) {
return existingIds.get(0);
}
// 分配新的workerId
for (long workerId = 0; workerId <= MAX_WORKER_ID; workerId++) {
String insertSql = "INSERT INTO worker_id_allocation (worker_id, application_name, ip, port, create_time) VALUES (?, ?, ?, ?, NOW())";
try {
jdbcTemplate.update(insertSql, workerId, applicationName, ip, port);
return workerId;
} catch (DuplicateKeyException e) {
// workerId已被占用,继续尝试下一个
continue;
}
}
throw new RuntimeException("No available worker ID");
}
}
优缺点分析
优点:
- 性能高,本地生成无网络开销
- 趋势递增,有利于数据库性能
- 可反解出时间戳和机器信息
- 不依赖外部系统,可用性高
缺点:
- 依赖系统时钟,需要处理时钟回拨
- 需要分配和管理workerId
- 41位时间戳有使用期限(约69年)
- 单节点并发能力有限(4096/毫秒)
Leaf算法(美团)
原理
美团开源的分布式ID生成系统,提供两种模式:Leaf-segment(号段模式)和Leaf-snowflake(雪花模式)。
Leaf-segment模式
@Component
public class LeafSegmentGenerator {
@Autowired
private JdbcTemplate jdbcTemplate;
private final Map<String, SegmentBuffer> segmentBuffers = new ConcurrentHashMap<>();
public long generateId(String tag) {
SegmentBuffer buffer = segmentBuffers.computeIfAbsent(tag, k -> new SegmentBuffer());
if (!buffer.isInitialized()) {
synchronized (buffer) {
if (!buffer.isInitialized()) {
updateSegment(buffer, tag);
buffer.setInitialized(true);
}
}
}
return getIdFromBuffer(buffer, tag);
}
private long getIdFromBuffer(SegmentBuffer buffer, String tag) {
while (true) {
Segment current = buffer.getCurrent();
if (current.getIdle() > 0) {
return current.nextId();
}
synchronized (buffer) {
if (current.getIdle() == 0) {
updateSegment(buffer, tag);
}
}
}
}
private void updateSegment(SegmentBuffer buffer, String tag) {
Segment current = buffer.getCurrent();
Segment next = buffer.getNext();
if (next == null) {
Segment newSegment = fetchSegment(tag);
buffer.setNext(newSegment);
buffer.switchPos();
buffer.setNext(null);
} else {
buffer.switchPos();
buffer.setNext(null);
// 异步预加载下一个号段
CompletableFuture.runAsync(() -> {
Segment newSegment = fetchSegment(tag);
buffer.setNext(newSegment);
});
}
}
private Segment fetchSegment(String tag) {
return jdbcTemplate.execute(connection -> {
connection.setAutoCommit(false);
try {
// 获取当前号段信息
String selectSql = "SELECT max_id, step FROM leaf_alloc WHERE biz_tag = ? FOR UPDATE";
PreparedStatement selectStmt = connection.prepareStatement(selectSql);
selectStmt.setString(1, tag);
ResultSet rs = selectStmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException("Business tag not found: " + tag);
}
long maxId = rs.getLong("max_id");
int step = rs.getInt("step");
// 更新号段信息
String updateSql = "UPDATE leaf_alloc SET max_id = max_id + step WHERE biz_tag = ?";
PreparedStatement updateStmt = connection.prepareStatement(updateSql);
updateStmt.setString(1, tag);
updateStmt.executeUpdate();
connection.commit();
return new Segment(maxId + 1, maxId + step);
} catch (Exception e) {
connection.rollback();
throw new RuntimeException("Failed to fetch segment", e);
}
});
}
private static class Segment {
private final AtomicLong currentId;
private final long maxId;
public Segment(long startId, long maxId) {
this.currentId = new AtomicLong(startId);
this.maxId = maxId;
}
public long nextId() {
return currentId.getAndIncrement();
}
public long getIdle() {
return maxId - currentId.get() + 1;
}
}
private static class SegmentBuffer {
private final Segment[] segments = new Segment[2];
private volatile int currentPos = 0;
private volatile boolean initialized = false;
private volatile Segment next = null;
public Segment getCurrent() {
return segments[currentPos];
}
public Segment getNext() {
return next;
}
public void setNext(Segment next) {
this.next = next;
}
public void switchPos() {
currentPos = 1 - currentPos;
segments[currentPos] = next;
}
public boolean isInitialized() {
return initialized;
}
public void setInitialized(boolean initialized) {
this.initialized = initialized;
}
}
}
Leaf-snowflake模式
@Component
public class LeafSnowflakeGenerator {
private final SnowflakeIdGenerator snowflakeGenerator;
private final ZooKeeper zooKeeper;
private final String zkPath;
public LeafSnowflakeGenerator(String zkServers, String zkPath) throws Exception {
this.zooKeeper = new ZooKeeper(zkServers, 3000, null);
this.zkPath = zkPath;
this.snowflakeGenerator = new SnowflakeIdGenerator(allocateWorkerId());
// 启动时钟检查线程
startClockCheckThread();
}
private long allocateWorkerId() throws Exception {
// 创建临时顺序节点
String nodePath = zooKeeper.create(
zkPath + "/worker-",
null,
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL
);
// 从节点路径中提取workerId
String nodeId = nodePath.substring(nodePath.lastIndexOf("-") + 1);
return Long.parseLong(nodeId) % 1024;
}
private void startClockCheckThread() {
Thread clockCheckThread = new Thread(() -> {
while (true) {
try {
// 定期检查时钟是否回拨
checkClock();
Thread.sleep(1000);
} catch (Exception e) {
// 处理时钟回拨异常
handleClockBackwards();
}
}
});
clockCheckThread.setDaemon(true);
clockCheckThread.start();
}
private void checkClock() throws Exception {
Stat stat = zooKeeper.exists(zkPath + "/clock", false);
if (stat != null) {
byte[] data = zooKeeper.getData(zkPath + "/clock", false, stat);
long zkTime = Long.parseLong(new String(data));
long localTime = System.currentTimeMillis();
if (localTime < zkTime) {
throw new RuntimeException("Clock moved backwards");
}
}
// 更新ZooKeeper时间
zooKeeper.setData(zkPath + "/clock", String.valueOf(System.currentTimeMillis()).getBytes(), -1);
}
private void handleClockBackwards() {
// 时钟回拨处理逻辑
try {
// 重新分配workerId
long newWorkerId = allocateWorkerId();
// 重新创建雪花算法生成器
snowflakeGenerator = new SnowflakeIdGenerator(newWorkerId);
} catch (Exception e) {
throw new RuntimeException("Failed to handle clock backwards", e);
}
}
public long generateId() {
return snowflakeGenerator.generateId();
}
}
优缺点分析
优点:
- 双模式支持,灵活选择
- 高可用性设计
- 性能优异
- 监控完善
缺点:
- 实现复杂
- 需要额外依赖(ZooKeeper)
- 运维成本高
UidGenerator算法(百度)
原理
百度开源的分布式ID生成器,基于Snowflake算法改进,支持自定义workerId分配和RingBuffer缓存。
核心实现
@Component
public class UidGenerator {
private final RingBuffer ringBuffer;
private final WorkerIdAssigner workerIdAssigner;
public UidGenerator(WorkerIdAssigner workerIdAssigner) {
this.workerIdAssigner = workerIdAssigner;
this.ringBuffer = new RingBuffer(1024);
initialize();
}
private void initialize() {
long workerId = workerIdAssigner.assignWorkerId();
UidProvider uidProvider = new DefaultUidProvider(workerId);
// 启动填充线程
Thread fillThread = new Thread(() -> {
while (true) {
try {
ringBuffer.put(uidProvider.provide());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
fillThread.setDaemon(true);
fillThread.start();
}
public long generateId() throws InterruptedException {
return ringBuffer.take();
}
}
class DefaultUidProvider implements UidProvider {
private static final long START_TIMESTAMP = 1262275200000L; // 2010-01-01
private final long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public DefaultUidProvider(long workerId) {
this.workerId = workerId;
}
@Override
public synchronized long provide() {
long currentTimestamp = getCurrentTimestamp();
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (currentTimestamp == lastTimestamp) {
sequence = (sequence + 1) & 4095; // 12位序列号
if (sequence == 0) {
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = currentTimestamp;
return ((currentTimestamp - START_TIMESTAMP) << 22)
| (workerId << 13)
| sequence;
}
private long waitNextMillis(long currentTimestamp) {
while (currentTimestamp <= lastTimestamp) {
currentTimestamp = getCurrentTimestamp();
}
return currentTimestamp;
}
private long getCurrentTimestamp() {
return System.currentTimeMillis();
}
}
class RingBuffer {
private static final int BUFFER_SIZE = 1024;
private final long[] buffer;
private final AtomicLong putIndex = new AtomicLong(0);
private final AtomicLong takeIndex = new AtomicLong(0);
public RingBuffer(int size) {
this.buffer = new long[size];
}
public void put(long value) throws InterruptedException {
long currentPut = putIndex.get();
long currentTake = takeIndex.get();
// 检查缓冲区是否已满
while (currentPut - currentTake >= BUFFER_SIZE) {
Thread.sleep(1);
currentTake = takeIndex.get();
}
buffer[(int)(currentPut % BUFFER_SIZE)] = value;
putIndex.incrementAndGet();
}
public long take() throws InterruptedException {
long currentTake = takeIndex.get();
// 等待直到有数据可用
while (putIndex.get() <= currentTake) {
Thread.sleep(1);
}
long value = buffer[(int)(currentTake % BUFFER_SIZE)];
takeIndex.incrementAndGet();
return value;
}
}
WorkerId分配策略
@Component
public class DisposableWorkerIdAssigner implements WorkerIdAssigner {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public long assignWorkerId() {
// 获取本机IP
String ip = getLocalIp();
// 尝试获取已有的workerId
String selectSql = "SELECT worker_id FROM worker_node WHERE host = ? AND port = ?";
List<Long> existingIds = jdbcTemplate.query(selectSql,
new Object[]{ip, getProcessId()},
(rs, rowNum) -> rs.getLong("worker_id"));
if (!existingIds.isEmpty()) {
return existingIds.get(0);
}
// 分配新的workerId
String insertSql = "INSERT INTO worker_node (host, port, type, launch_date, modified) VALUES (?, ?, ?, NOW(), NOW())";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, ip);
ps.setString(2, getProcessId());
ps.setString(3, "ACTUAL");
return ps;
}, keyHolder);
return keyHolder.getKey().longValue() % 1024;
}
private String getLocalIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
throw new RuntimeException("Failed to get local IP", e);
}
}
private String getProcessId() {
return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
}
}
优缺点分析
优点:
- 性能极高,RingBuffer缓存
- 支持自定义workerId分配
- 可扩展性强
- 监控完善
缺点:
- 实现复杂
- 需要数据库支持
- 依赖较多
TinyID算法(滴滴)
原理
滴滴开源的分布式ID生成系统,基于号段模式,支持多db、高可用、动态扩容。
核心实现
@Service
public class TinyIdService {
@Autowired
private TinyIdGeneratorFactory factory;
public Long generateId(String bizType) {
return factory.getGenerator(bizType).nextId();
}
public List<Long> generateIds(String bizType, int batchSize) {
return factory.getGenerator(bizType).nextId(batchSize);
}
}
@Component
public class TinyIdGeneratorFactory {
@Autowired
private TinyIdGenerator[] generators;
private final Map<String, TinyIdGenerator> generatorMap = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
for (TinyIdGenerator generator : generators) {
generatorMap.put(generator.getBizType(), generator);
}
}
public TinyIdGenerator getGenerator(String bizType) {
TinyIdGenerator generator = generatorMap.get(bizType);
if (generator == null) {
throw new RuntimeException("Generator not found for bizType: " + bizType);
}
return generator;
}
}
public class TinyIdGenerator {
private final String bizType;
private final SegmentIdService segmentIdService;
private final SegmentBuffer segmentBuffer;
public TinyIdGenerator(String bizType, SegmentIdService segmentIdService) {
this.bizType = bizType;
this.segmentIdService = segmentIdService;
this.segmentBuffer = new SegmentBuffer();
}
public synchronized long nextId() {
Segment current = segmentBuffer.getCurrent();
if (current == null || current.isEmpty()) {
loadCurrentSegment();
current = segmentBuffer.getCurrent();
}
long id = current.nextId();
// 当前号段用完,切换到下一个
if (current.isEmpty()) {
switchSegment();
}
// 异步加载下一个号段
if (current.getIdle() < current.getTotal() * 0.2) {
CompletableFuture.runAsync(this::loadNextSegment);
}
return id;
}
public List<Long> nextId(int batchSize) {
List<Long> ids = new ArrayList<>(batchSize);
for (int i = 0; i < batchSize; i++) {
ids.add(nextId());
}
return ids;
}
private void loadCurrentSegment() {
Segment segment = segmentIdService.getNextSegment(bizType);
segmentBuffer.setCurrent(segment);
}
private void loadNextSegment() {
Segment segment = segmentIdService.getNextSegment(bizType);
segmentBuffer.setNext(segment);
}
private void switchSegment() {
Segment next = segmentBuffer.getNext();
if (next == null) {
loadNextSegment();
next = segmentBuffer.getNext();
}
segmentBuffer.setCurrent(next);
segmentBuffer.setNext(null);
}
public String getBizType() {
return bizType;
}
}
@Service
public class SegmentIdService {
@Autowired
private DataSource dataSource;
public Segment getNextSegment(String bizType) {
return executeTransaction(connection -> {
// 获取当前号段信息
String selectSql = "SELECT max_id, step, delta, remainder FROM tiny_id_info WHERE biz_type = ? FOR UPDATE";
PreparedStatement selectStmt = connection.prepareStatement(selectSql);
selectStmt.setString(1, bizType);
ResultSet rs = selectStmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException("Business type not found: " + bizType);
}
long maxId = rs.getLong("max_id");
int step = rs.getInt("step");
int delta = rs.getInt("delta");
int remainder = rs.getInt("remainder");
// 计算新的号段
long newMaxId = maxId + step;
// 更新号段信息
String updateSql = "UPDATE tiny_id_info SET max_id = ?, update_time = NOW() WHERE biz_type = ?";
PreparedStatement updateStmt = connection.prepareStatement(updateSql);
updateStmt.setLong(1, newMaxId);
updateStmt.setString(2, bizType);
updateStmt.executeUpdate();
// 记录号段获取日志
String insertLogSql = "INSERT INTO tiny_id_token (biz_type, token, remark, create_time) VALUES (?, ?, ?, NOW())";
PreparedStatement insertLogStmt = connection.prepareStatement(insertLogSql);
insertLogStmt.setString(1, bizType);
insertLogStmt.setLong(2, newMaxId);
insertLogStmt.setString(3, "Generated segment");
insertLogStmt.executeUpdate();
return new Segment(maxId + 1, newMaxId, delta, remainder);
});
}
private <T> T executeTransaction(ConnectionCallback<T> callback) {
return new JdbcTemplate(dataSource).execute(callback);
}
}
class Segment {
private final AtomicLong currentId;
private final long maxId;
private final int delta;
private final int remainder;
public Segment(long startId, long maxId, int delta, int remainder) {
this.currentId = new AtomicLong(startId);
this.maxId = maxId;
this.delta = delta;
this.remainder = remainder;
}
public long nextId() {
long id = currentId.getAndAdd(delta);
if (id > maxId) {
throw new RuntimeException("Segment exhausted");
}
return id;
}
public boolean isEmpty() {
return currentId.get() > maxId;
}
public long getIdle() {
return maxId - currentId.get() + 1;
}
public long getTotal() {
return maxId - (currentId.get() - delta) + 1;
}
}
多数据库支持
@Configuration
public class TinyIdDataSourceConfig {
@Bean
public Map<String, DataSource> dataSourceMap() {
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 主库
HikariConfig masterConfig = new HikariConfig();
masterConfig.setJdbcUrl("jdbc:mysql://master:3306/tinyid");
masterConfig.setUsername("root");
masterConfig.setPassword("password");
dataSourceMap.put("master", new HikariDataSource(masterConfig));
// 从库1
HikariConfig slave1Config = new HikariConfig();
slave1Config.setJdbcUrl("jdbc:mysql://slave1:3306/tinyid");
slave1Config.setUsername("root");
slave1Config.setPassword("password");
dataSourceMap.put("slave1", new HikariDataSource(slave1Config));
// 从库2
HikariConfig slave2Config = new HikariConfig();
slave2Config.setJdbcUrl("jdbc:mysql://slave2:3306/tinyid");
slave2Config.setUsername("root");
slave2Config.setPassword("password");
dataSourceMap.put("slave2", new HikariDataSource(slave2Config));
return dataSourceMap;
}
@Bean
public SegmentIdService segmentIdService() {
return new MultiDataSourceSegmentIdService(dataSourceMap());
}
}
public class MultiDataSourceSegmentIdService extends SegmentIdService {
private final Map<String, DataSource> dataSourceMap;
private final AtomicInteger counter = new AtomicInteger(0);
public MultiDataSourceSegmentIdService(Map<String, DataSource> dataSourceMap) {
this.dataSourceMap = dataSourceMap;
}
@Override
public Segment getNextSegment(String bizType) {
// 轮询选择数据源
List<String> dataSourceNames = new ArrayList<>(dataSourceMap.keySet());
String dataSourceName = dataSourceNames.get(counter.getAndIncrement() % dataSourceNames.size());
DataSource dataSource = dataSourceMap.get(dataSourceName);
return executeTransaction(dataSource, connection -> {
// 使用选定的数据源获取号段
return super.getNextSegment(bizType);
});
}
private <T> T executeTransaction(DataSource dataSource, ConnectionCallback<T> callback) {
return new JdbcTemplate(dataSource).execute(callback);
}
}
优缺点分析
优点:
- 支持多数据库,高可用
- 性能优异,异步预加载
- 支持动态扩容
- 监控完善
缺点:
- 实现复杂
- 需要维护数据库集群
- 运维成本高
各种实现方式对比
| 实现方式 | 唯一性 | 性能 | 可用性 | 趋势递增 | 长度 | 实现复杂度 | 运维成本 |
|---|---|---|---|---|---|---|---|
| UUID | 高 | 极高 | 极高 | 否 | 长(36位) | 简单 | 低 |
| 数据库自增ID | 高 | 低 | 低 | 是 | 短 | 简单 | 中 |
| 数据库号段 | 高 | 高 | 中 | 是 | 短 | 中等 | 中 |
| Redis | 高 | 高 | 中 | 是 | 短 | 中等 | 高 |
| Snowflake | 高 | 极高 | 高 | 是 | 短 | 中等 | 低 |
| Leaf | 高 | 极高 | 高 | 是 | 短 | 复杂 | 高 |
| UidGenerator | 高 | 极高 | 高 | 是 | 短 | 复杂 | 高 |
| TinyID | 高 | 极高 | 高 | 是 | 短 | 复杂 | 高 |
选择指南与最佳实践
选择原则
-
业务规模
- 小规模业务:UUID、数据库自增ID
- 中等规模:数据库号段、Redis
- 大规模:Snowflake、Leaf、UidGenerator、TinyID
-
性能要求
- 低并发:数据库自增ID、UUID
- 中等并发:数据库号段、Redis
- 高并发:Snowflake、Leaf、UidGenerator、TinyID
-
可用性要求
- 一般可用性:数据库自增ID、UUID
- 高可用性:Redis集群、Snowflake、Leaf、UidGenerator、TinyID
-
运维能力
- 运维能力弱:UUID、Snowflake
- 运维能力中等:数据库号段、Redis
- 运维能力强:Leaf、UidGenerator、TinyID
最佳实践
-
小规模系统
// 使用UUID,简单可靠 public class SimpleIdGenerator { public static String generateId() { return UUID.randomUUID().toString().replace("-", ""); } } -
中等规模系统
// 使用数据库号段模式 @Service public class MediumScaleIdService { @Autowired private SegmentIdGenerator segmentGenerator; public long generateOrderId() { return segmentGenerator.generateId("order"); } public long generateUserId() { return segmentGenerator.generateId("user"); } } -
大规模系统
// 使用Snowflake算法 @Service public class LargeScaleIdService { private final SnowflakeIdGenerator[] generators; public LargeScaleIdService(int nodeCount) { this.generators = new SnowflakeIdGenerator[nodeCount]; for (int i = 0; i < nodeCount; i++) { generators[i] = new SnowflakeIdGenerator(i); } } public long generateId() { // 轮询选择生成器 int index = (int)(Thread.currentThread().getId() % generators.length); return generators[index].generateId(); } }
注意事项
-
时钟同步
- 使用NTP服务同步系统时间
- 监控时钟回拨情况
- 实现时钟回拨处理机制
-
WorkerId管理
- 建立workerId分配和管理机制
- 避免workerId冲突
- 支持动态workerId分配
-
监控告警
- 监控ID生成性能
- 监控ID使用速率
- 设置合理的告警阈值
-
容灾备份
- 实现多机房部署
- 建立降级机制
- 定期备份ID生成状态
实际应用案例
案例1:电商订单系统
需求: 高并发、趋势递增、可反解
方案: Snowflake算法
@Service
public class OrderIdGenerator {
private final SnowflakeIdGenerator generator;
public OrderIdGenerator() {
long workerId = getWorkerIdFromConfig();
this.generator = new SnowflakeIdGenerator(workerId);
}
public long generateOrderId() {
return generator.generateId();
}
public OrderInfo parseOrderId(long orderId) {
SnowflakeIdGenerator.IdInfo idInfo = SnowflakeIdGenerator.parseId(orderId);
return new OrderInfo(orderId, idInfo.getTimestamp(), idInfo.getWorkerId());
}
}
案例2:用户系统
需求: 全球唯一、信息安全
方案: UUID + 数据库
@Service
public class UserIdGenerator {
@Autowired
private UserRepository userRepository;
public String generateUserId() {
String userId;
do {
userId = UUID.randomUUID().toString().replace("-", "");
} while (userRepository.existsById(userId));
return userId;
}
}
案例3:分布式追踪系统
需求: 全局唯一、可反解、高性能
方案: 改进版Snowflake
public class TraceIdGenerator {
private static final long START_TIME = 1609430400000L; // 2021-01-01
private static final long WORKER_ID_BITS = 10L;
private static final long SEQUENCE_BITS = 12L;
private final long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public String generateTraceId() {
long id = generateId();
return Long.toHexString(id);
}
private synchronized long generateId() {
long currentTimestamp = System.currentTimeMillis() - START_TIME;
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (currentTimestamp == lastTimestamp) {
sequence = (sequence + 1) & ((1 << SEQUENCE_BITS) - 1);
if (sequence == 0) {
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = currentTimestamp;
return (currentTimestamp << (WORKER_ID_BITS + SEQUENCE_BITS))
| (workerId << SEQUENCE_BITS)
| sequence;
}
}
总结
分布式ID生成是分布式系统中的核心问题,需要根据具体的业务场景、性能要求、可用性需求和运维能力选择合适的实现方案。从小规模的UUID到大規模的专用分布式ID生成系统,每种方案都有其适用场景和优缺点。在实际应用中,还需要考虑时钟同步、workerId管理、监控告警、容灾备份等因素,确保ID生成系统的稳定可靠运行。

被折叠的 条评论
为什么被折叠?



