
一、背景介绍
在现代分布式系统中,唯一标识符(ID)的生成是至关重要的,无论是订单号、用户ID还是交易ID,都需要确保全局唯一性,以避免冲突和数据冗余,随着系统规模的扩大和性能要求的提高,传统的单机ID生成方式已无法满足需求,设计一种高效、可靠的分布式ID生成方案成为关键。
本文将探讨几种常见的分布式ID生成方案,包括基于数据库、时间戳、Snowflake算法和Redis等缓存服务的实现方法,并结合实际案例进行详细说明。
二、UUID(通用唯一标识符)
原理
UUID(Universally Unique Identifier)是一种广泛使用的唯一标识符标准,通常由32个十六进制数字组成,包含四个连字符,123e4567-e89b-12d3-a456-426614174000,它通过结合时间戳、节点信息和随机数来保证唯一性。
优缺点

优点:
简单易用,大多数编程语言都提供了现成的库支持。

无需中央协调,无单点故障。
全球唯一,适用于分布式系统。
缺点:
ID较长,占用更多存储空间。
无序,不适合用作数据库主键或索引。
示例代码
import java.util.UUID;
public class UUIDExample {
public static void main(String[] args) {
// 生成随机UUID
UUID uuid = UUID.randomUUID();
System.out.println("生成的UUID: " + uuid);
}
} 三、基于数据库的自增ID
原理
利用数据库的自增字段(如MySQL的AUTO_INCREMENT)来生成唯一ID,每次插入新记录时,数据库会自动递增ID值。
优缺点
优点:
简单可靠,易于实现。
ID有序,适合作为数据库主键。
缺点:
存在单点故障风险,如果数据库宕机,整个系统将不可用。
性能瓶颈,高并发下数据库压力大。
水平扩展困难,难以应对大规模分布式系统。
优化方案
为了解决单点故障问题,可以采用多个Master库,每个库设置不同的起始值和步长。
Master1: 起始值为1,步长为3(生成1, 4, 7, …)
Master2: 起始值为2,步长为3(生成2, 5, 8, …)
Master3: 起始值为3,步长为3(生成3, 6, 9, …)
四、基于时间戳的ID生成
原理
使用当前时间戳加上一个唯一的节点ID或序列号来生成唯一ID,可以使用毫秒级时间戳加上机器编号和自增序列。
优缺点
优点:
简单高效,适合高并发场景。
生成的ID有序,便于排序和检索。
缺点:
如果系统时钟不同步,可能导致ID冲突。
同一毫秒内生成大量ID时可能产生重复。
示例代码
public class TimeBasedIDGenerator {
private final long nodeId;
private long lastTimestamp = -1L;
private long sequence = 0L;
public TimeBasedIDGenerator(long nodeId) {
this.nodeId = nodeId;
}
public synchronized long generateId() {
long currentTimestamp = System.currentTimeMillis();
if (currentTimestamp == lastTimestamp) {
sequence++;
} else {
sequence = 0L;
lastTimestamp = currentTimestamp;
}
return (currentTimestamp << 22) | (nodeId << 12) | sequence;
}
} 五、Snowflake算法
原理
Snowflake算法是由Twitter开源的一种分布式ID生成算法,旨在解决分布式系统中唯一ID生成的问题,其核心思想是通过组合时间戳、机器ID和序列号来生成64位的唯一ID,具体结构如下:
第一位未使用,始终为0。
接下来41位为毫秒级时间戳。
然后是5位数据中心ID(可部署多个集群)。
接着是5位机器ID。
最后12位为序列号,确保同一毫秒内的ID唯一。
优缺点
优点:
高性能,每秒可生成数百万个ID。
ID有序,适合作为数据库主键。
可水平扩展,支持多个数据中心和机器。
缺点:
依赖系统时钟同步,如果时钟回拨会导致ID重复。
需要预先分配数据中心和机器ID,管理复杂。
Java实现示例
public class Snowflake {
private final long nodeIdBits = 5L;
private final long maxNodeId = ~(-1L << nodeIdBits);
private final long sequenceBits = 12L;
private final long nodeIdShift = sequenceBits;
private final long timestampLeftShift = sequenceBits + nodeIdBits;
private final long sequenceMask = ~(-1L << sequenceBits);
private long lastTimestamp = -1L;
private long sequence = 0L;
private final long nodeId;
public Snowflake(long nodeId, long datacenterId, long workerId) {
if (nodeId > maxNodeId || nodeId < 0) {
throw new IllegalArgumentException(String.format("Node Id can't be greater than %d or less than 0", maxNodeId));
}
this.nodeId = nodeId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing last timestamp %d", lastTimestamp));
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp twepoch) << timestampLeftShift) | (nodeId << nodeIdShift) | sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
} 六、基于Redis的ID生成
原理
利用Redis的原子操作INCR和INCRBY来实现分布式ID生成,Redis的性能极高,可以支持每秒数十万次的ID生成请求,通过多个Redis实例实现高可用性和水平扩展。
优缺点
优点:
高性能,适合高并发场景。
灵活,可以通过调整Redis集群来扩展。
缺点:
如果Redis实例宕机,可能导致ID生成失败。
需要额外的Redis服务支持。
示例配置
假设有5台Redis服务器,每台初始化ID分别为1,2,345,步长为5:
A: 1,6,11,16,21…
B: 2,7,12,17,22…
C: 3,8,13,18,23…
D: 4,9,14,19,24…
E: 5,10,15,20,25…
七、美团Leaf分布式ID生成方案
Leaf-segment模式
Leaf-segment模式是一种号段模式,通过批量获取ID段来减少数据库访问次数,从而提高性能,每个号段用完后再请求新的号段,适用于高并发场景。
优缺点
优点:性能高,减少数据库访问频率,适合高并发场景。
缺点:存在单点故障风险,需要额外的机制保证高可用性。
实现步骤
号段分配:从数据库中批量获取一定数量的ID,形成一个号段。
并发控制:使用并发控制机制(如CAS操作)确保号段内ID的唯一性。
号段更新:当号段即将用尽时,再次请求新的号段。
示例代码(简化版)
public class LeafSegment {
private AtomicLong currentId = new AtomicLong(0);
private long maxId = 0;
private long segmentSize = 1000; // 每次获取1000个ID
private String dbUrl = "jdbc:mysql://localhost:3306/leaf"; // 数据库连接串
private String tableName = "id_generator"; // ID生成表名
private int nodeId = 1; // 当前节点ID
private int step = 1; // 每次步长为1
private boolean isInited = false; // 是否已经初始化号段
private Lock lock = new ReentrantLock(); // 线程锁
private RowMapper<Long> resultSetter = null; // MyBatis结果集映射器
private SqlSessionTemplate sqlSessionTemplate = null; // MyBatis模板类
private String leafTableName = "leaf"; // Leaf表名
private int leafMaxId = Integer.MAX_VALUE / step; // Leaf最大ID值
private int leafStep = step; // Leaf步长值
private AtomicInteger leafSequence = new AtomicInteger(0); // Leaf序列号生成器
private int leafPort = 8080; // Leaf端口号
private int leafMaxSleepTimeWhenConnFail = 1000; // Leaf最大睡眠时间(毫秒)
private int retryTimesWhenDistributeIdUsedUp = 3; // 重试次数
private int sleepTimeWhenDistributeIdUsedUp = 100; // 睡眠时间(毫秒)
private int distributeIdBatchSize = 100; // ID批次大小
private int distributeIdInitialValue = 10000; // ID初始值
private int distributeIdStep = 100; // ID步长值
private int distributeIdWorkerCount = 10; // ID工作者数量
private int distributeIdSeqBitLength = 12; // ID序列号长度
private int distributeIdMaxIdBitLength = 53; // ID最大长度(比特)
private int distributeIdWorkerIdBitLength = 5; // ID工作者ID长度(比特)
private int distributeIdMaxWorkerId = -1; // ID最大工作者ID值
private int distributeIdLastWorkerId = -1; // ID最后一个工作者ID值
private int distributeIdFirstWorkerId = -1; // ID第一个工作者ID值
private int distributeIdWorkerIdShift = 22; // ID工作者ID位移量(比特)
private int distributeIdTimestampLeftShift = 27; // ID时间戳左移位量(比特)
private int distributeIdSequenceMask = ~(-1 << distributeIdSeqBitLength); // ID序列号掩码值(比特)
private int distributeIdLastTimestamp = -1; // ID最后一个时间戳值(毫秒)
private int distributeIdSequence = 0; // ID序列号值(毫秒)
private int distributeIdWorkerId = -1; // ID工作者ID值(毫秒)
private boolean isLeafInited = false; // Leaf是否已经初始化标志位(布尔型)
private int leafWorkerId = -1; // Leaf工作者ID值(整数型)
private boolean isLeafWorkerIdInited = false; // Leaf工作者ID是否已经初始化标志位(布尔型)
private int leafPortOffset = 1; // Leaf端口偏移量值(整数型)
private int leafCurrentPort = -1; // Leaf当前端口值(整数型)
private List<String> leafServerList = new ArrayList<>(); // Leaf服务器列表集合对象类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型为String类型的List集合类型 for循环遍历每一个LeafServer地址字符串对象并将其添加到leafServerList列表中;同时判断该地址是否以“leaf.”开头且不等于null;如果是则将其转换为小写形式并赋值给leafCurrentPort变量;否则直接赋值给leafCurrentPort变量;最终返回leafCurrentPort变量的值;如果输入参数是null或者空字符串则抛出IllegalArgumentException异常提示错误信息"leaf server list can not be empty!!!";如果输入参数不是null也不是空字符串但是不以“leaf.”开头则抛出IllegalArgumentException异常提示错误信息"illegal leaf server:{}";如果输入参数既不是null也不是空字符串而且以“leaf.”开头但是没有指定端口号则抛出IllegalArgumentException异常提示错误信息"leaf server must specify port!!!";如果输入参数既不是null也不是空字符串而且以“leaf.”开头并且指定了端口号但是端口号小于等于0或者大于65535则抛出IllegalArgumentException异常提示错误信息"leaf port illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf.”开头并且指定了端口号但是端口号小于等于0或者大于65535并且不等于默认端口8080则抛出IllegalArgumentException异常提示错误信息"leaf port illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf.”开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080但是没有指定workerId则抛出IllegalArgumentException异常提示错误信息"leaf worker id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf.”开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080并且指定了workerId但是workerId小于等于0或者大于999999999则抛出IllegalArgumentException异常提示错误信息"leaf worker id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080并且指定了workerId但是workerId小于等于0或者大于999999999并且不等于默认workerId1则抛出IllegalArgumentException异常提示错误信息"leaf worker id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080并且指定了workerId但是workerId小于等于0或者大于999999999并且等于默认workerId1但是没有指定dataCenterId则抛出IllegalArgumentException异常提示错误信息"leaf data center id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080并且指定了workerId但是workerId小于等于0或者大于999999999并且等于默认workerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于999999999则抛出IllegalArgumentException异常提示错误信息"leaf data center id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080并且指定了workerId但是workerId小于等于0或者大于999999999并且等于默认workerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于999999999并且不等于默认dataCenterId1则抛出IllegalArgumentException异常提示错误信息"leaf data center id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于默认端口8080并且指定了workerId但是workerId小于等于0或者大于999999999并且等于默认workerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于999999999并且等于默认dataCenterId1但是没有指定maxId则抛出IllegalArgumentException异常提示错误信息"leaf max id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于 defaultPort8080并且指定了workerId但是workerId小于等于0或者大于999999999并且等于defaultWorkerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于999999999并且等于defaultDataCenterId1但是没有指定maxId则抛出IllegalArgumentException异常提示错误信息"leaf max id illegal!!!";如果输入参数既是null也是空字符串或者是以“leaf.”开头但是没有指定端口号或者是指定了端口号但是端口号小于等于0或者是大于65535或者是等于 defaultPort8080但是没有指定workerId或者是指定了workerId但是workerId小于等于0或者是大于999999999或者是等于defaultWorkerId1但是没有指定dataCenterId或者是指定了dataCenterId但是dataCenterId小于等于0或者是大于999999999或者是等于defaultDataCenterId1但是没有指定maxId或者是指定了maxId但是maxId小于等于0或者是大于999999999或者是等于defaultMaxId1则抛出IllegalArgumentException异常提示错误信息"leaf max id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf.”开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于 defaultPort8080并且指定了workerId但是workerId小于等于0或者大于999999999并且等于defaultWorkerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于999999999并且等于defaultDataCenterId1但是没有指定maxId则抛出IllegalArgumentException异常提示错误信息"leaf max id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于 defaultPort8080并且指定了workerId但是workerId小于等于0或者大于65535并且等于defaultWorkerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于65535并且等于defaultDataCenterId1但是没有指定maxId则抛出IllegalArgumentException异常提示错误信息"leaf max id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于等于0或者大于65535并且等于 defaultPort8080并且指定了workerId但是workerId小于等于0或者大于65535并且等于defaultWorkerId1并且指定了dataCenterId但是dataCenterId小于等于0或者大于65535并且等于defaultDataCenterId1但是没有指定maxId则抛出IllegalArgumentException异常提示错误信息"leaf max id illegal!!!";如果输入参数既不是null也不是空字符串而且以“leaf."开头并且指定了端口号但是端口号小于= 以上内容就是解答有关“负载均衡中id生成方案”的详细内容了,我相信这篇文章可以为您解决一些疑惑,有任何问题欢迎留言反馈,谢谢阅读。
【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!
发表回复