1. 什么是全局唯一ID
全局唯一ID(Globally Unique Identifier)是指在分布式系统、多服务、多数据库等跨节点环境中,能够保证全局范围内不重复的标识符。它通常用于主键、订单号、消息ID、用户ID等场景,是分布式架构中数据一致性、链路追踪和业务去重的基础。
一个合格的全局唯一ID通常需要满足以下几个特性:
- 全局唯一性:在任何节点、任何时间生成的ID都不会重复。都需要保证唯一性。
- 趋势递增:在多数业务场景下,ID最好随时间递增,便于数据库索引和排序。
- 高可用:ID生成服务不能成为系统瓶颈,需要支持高并发。
- 安全性:ID不应泄露业务规模或可被轻易猜测,避免被恶意遍历。
2. 为什么需要全局唯一ID
在单体应用中,数据库自增主键即可满足唯一性需求。但在分布式系统中,数据分散在多个数据库、多个服务实例中,如果每个节点都使用本地自增ID,就会出现重复。因此需要一套跨节点的ID生成方案,保证全局唯一。
典型场景包括:
- 订单系统:多个订单服务实例同时生成订单号,不能重复。
- 消息队列:每条消息需要唯一ID用于去重和追踪。
- 分库分表:数据拆分到多张表后,主键不能依赖单库自增。
- 日志链路:分布式调用链需要全局唯一的TraceID串联日志。
3. 常见的全局唯一ID构建方案
业界常用的全局唯一ID方案主要有以下几种,各有优缺点,需要根据业务场景选择。
3.1 UUID
UUID(Universally Unique Identifier)是最简单直接的方案,由128位二进制组成,通常表示为32位十六进制字符串。Java中可通过 UUID.randomUUID() 生成。
java
import java.util.UUID;
public class UuidDemo {
public static void main(String[] args) {
String id = UUID.randomUUID().toString().replace("-", "");
System.out.println(id);
}
}
优点:生成简单、无需网络交互、本地即可保证唯一。缺点:无序、长度较长(36位含连字符),作为数据库主键时会导致索引碎片,查询性能下降。因此在实际的业务中很少被用到,只有在那些极少数的情况下(对数据的顺序结构没有要求的时候)会用到。
3.2 数据库自增ID
利用数据库的自增主键生成ID,在单库场景下简单可靠。但在分布式场景下,可以通过设置不同的起始值和步长来避免冲突。这个方案同样的也很少用到,我们的项目的数据库大多都是集群的形式出现,并不会出现一个单独的数据库,一个数据库如果出现了问题的话就会很麻烦,因此我们并不会只布置一台数据库。
sql
-- 节点1
CREATE TABLE t_id (
id BIGINT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
) AUTO_INCREMENT = 1;
-- 节点2
CREATE TABLE t_id (
id BIGINT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
) AUTO_INCREMENT = 2;
优点:实现简单、趋势递增。缺点:依赖数据库,高并发下数据库成为瓶颈;多节点配置复杂,扩展性差。
3.3 Redis生成ID
利用Redis的原子自增命令 INCR 或 INCRBY 生成ID,可以结合日期前缀实现趋势递增。
java
import redis.clients.jedis.Jedis;
public class RedisIdDemo {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
long id = jedis.incr("order:id");
System.out.println("生成的ID: " + id);
}
}
}
优点:性能高、实现简单、天然趋势递增。缺点:依赖Redis的可用性,如果Redis宕机可能影响ID生成;需要额外维护Redis集群。
3.4 雪花算法(Snowflake)
雪花算法是Twitter开源的一种分布式ID生成算法,由64位组成,结构如下:
text
0 | 41位时间戳 | 10位机器ID | 12位序列号
其中第1位为符号位固定为0,41位时间戳可支持约69年,10位机器ID可部署1024个节点,12位序列号每毫秒可生成4096个ID。
java
public class SnowflakeIdWorker {
private final long twepoch = 1288834974657L;
private final long workerIdBits = 10L;
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long timestampLeftShift = sequenceBits + workerIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdWorker(long workerId) {
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift)
| (workerId << workerIdShift)
| sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
优点:性能极高、趋势递增、不依赖外部组件。缺点:依赖机器时钟,时钟回拨会导致ID重复,需要做好时钟同步和回拨处理。
3.5 美团Leaf方案
美团开源的Leaf提供了两种模式:号段模式和雪花模式。号段模式通过批量获取数据库号段,减少数据库压力;雪花模式在雪花算法基础上解决了时钟回拨问题。
text
号段模式:每次从数据库取一批ID(如1000个),缓存在本地内存中,用完再取下一批。
优点:兼顾性能和可用性,适合大规模分布式系统。缺点:实现复杂度较高,需要引入额外组件。
4. 方案对比与选型建议
| 方案 | 唯一性 | 趋势递增 | 性能 | 依赖 | 适用场景 |
|---|---|---|---|---|---|
| UUID | 高 | 否 | 高 | 无 | 非主键场景、离线生成 |
| 数据库自增 | 高 | 是 | 低 | 数据库 | 单库、低并发 |
| Redis | 高 | 是 | 中 | Redis | 中高并发、趋势递增 |
| 雪花算法 | 高 | 是 | 极高 | 无 | 高并发、分布式 |
| 美团Leaf | 高 | 是 | 极高 | 数据库+ZK | 大规模分布式系统 |
选型建议:中小型系统优先考虑雪花算法或Redis方案;对ID有序性要求高且并发量大的场景,推荐美团Leaf号段模式;如果只是临时生成唯一标识且不关心顺序,UUID即可满足。
5. 总结
全局唯一ID是分布式系统的基础设施之一,核心目标是保证跨节点唯一。不同方案在唯一性、递增性、性能和依赖上有明显差异,实际选型时应结合业务规模、并发量、可用性要求和运维成本综合权衡。雪花算法因其高性能和趋势递增特性,是目前最主流的方案;美团Leaf则在雪花基础上进一步解决了时钟回拨等工程问题,适合大规模生产环境。