常见的全局 ID 生成方案
全局 ID 在分布式场景中很常见,主要目的是为了唯一标识某一事物。 例如要标识一位用户,若是采用数据库自增主键作为用户 ID,遇到分库分表这样的场景,就可能会造成用户 ID 冲突的情况。 现在主流的全局 ID 的方案主要有着如下这些:
- UUID
- 雪花 ID
- 基于 Redis/ZooKeeper 进行生成
1 UUID
1.1 什么是 UUID?
UUID 是一种 128 位的全局唯一标识符,广泛应用于分布式系统中,确保 ID 的全局唯一性,UUID 的生成基于算法,因此可以在不同设备、不同系统上独立生成。
1.2 UUID 的结构
UUID 通常表示为 32 个十六进制字符,中间使用连字符分为 5 组,格式如下:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
x:十六进制字符(0-9 和 a-f)。
M:表示 UUID 的版本号(1-5)。
N:前两位表示 UUID 的变种(variant)。
例如:e4cc3d44-3b3a-4f6a-855c-62f2a87f27aa
UUID 因为有着 6 或 7 位用于决定 UUID 的版本和变种,所以 UUID 的随机性往往由剩下的 122 位决定。
1.3 UUID 的优缺点
UUID 有着不同的版本,不同版本的优缺点亦不相同,这里以目前用的最多的 v4 版本为例:
优点:
- UUID 的随机性依赖于其生成算法和随机数源,122 位的随机数组合提供了 2^122 种可能性,可以说,不会出现冲突的可能。
- 无需依赖任何外部的配置,完全的去中心化。
缺点:
- 128 位的长度较大,增加了储存的成本。
- 因为是完全随机,不能基于 UUID 进行排序,若是作为数据库的主键,会导致查询效率较低。
1.4 UUID 使用实例
Java 原生库中就有着 UUID 的工具类,使用起来非常简单:
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
2 雪花 ID
2.1 什么是雪花 ID?
雪花 ID 是由 Twitter 提出的一个分布式唯一 ID 生成算法,能够在不依赖中心化的服务的情况下,在分布式系统中生成全局唯一且递增的 64 位 ID。 它的核心特点是生成的 ID 可以按照时间顺序排序,同时避免了在高并发场景下的冲突。
2.2 雪花 ID 的结构
雪花 ID 总共是 64 位:
- 1 位:符号位,始终为 0。
- 41 位:时间戳(毫秒精度),从某个固定的时间点开始计算。
- 10 位:机器 ID,标识生成雪花 ID 的机器。
- 12 位:序列号,通过递增的方式,用于同一机器、同一毫秒,生成不同的雪花 ID。
2.3 雪花 ID 的优缺点
优点:
- 高效生成:因为无需依赖网络或数据库,且算法相对简单,在高并发下亦能快速生成。
- 一定的有序性:因为高位为时间戳,对于同一机器生成的雪花 ID 可基于时间顺序进行排序。
- 去中心化:不依赖中心服务,无需集中协调。
- 节省储存空间:雪花 ID 为 64 位整数,占用空间相对较小。
缺点:
- 依赖于机器:因为有着机器 ID,所以雪花 ID 的生成依赖于机器。
- 时钟回拨问题:如果系统时间发生回退,可能会导致雪花 ID 的重复或是不按预期进行排序。
雪花 ID 如何解决时钟回拨问题?
- 等待模式:检测到时钟回拨时,雪花 ID 生成器进入到等待状态,直到系统时间回到回拨之前的状态。
- 递增序列号:为每个时刻生成的雪花 ID 分配一个递增的序列号,当检测到时钟回拨时,获取到序列号最大的雪花 ID 的时间戳,基于该时间戳进行适当调整后继续生成新的雪花 ID。
2.4 雪花 ID 使用实例
基于 hutool 工具包,封装一个雪花 ID 生成器,用于生成全局唯一的用户 ID:
import cn.hutool.core.lang.Snowflake;
public class SnowflakeIdGenerator {
// Snowflake ID 生成器
private static Snowflake snowflake;
// 初始化 Snowflake 生成器,传入机器 ID 和 数据中心 ID
static {
snowflake = new Snowflake(1L, 1L);
}
// 生成用户 ID
public static long generateUserId() {
return snowflake.nextId();
}
}
3 基于 Redis/ZooKeeper 进行生成
相比 UUID 与 雪花 ID,基于 Redis/ZooKeeper 进行生成的方案是一种中心化的方案,依赖于中间件 Redis 或是 ZooKeeper。
核心思想就是利用到了 Redis 的 string 类型的自增的特点,而对于 ZooKeeper 则是使用到了其顺序节点的特性。
若是项目中本身就使用到了 Redis 或 ZooKeeper,使用这种方案简单直接,且能保证全局的顺序性(这是 UUID 和雪花 ID 所做不到的)
但因为依赖于中间件,所以 ID 的生成会产生一定的网络开销,对网络延迟敏感,且一旦 Redis 或 ZooKeeper 故障,ID 就将无法生成。
4 总结
UUID 适用场景:
- 完全的去中心化
- 对 ID 生成的性能与 ID 所占储存空间无太大要求
- 不要求 ID 的有序性
雪花 ID 适用场景:
- 去中心化但依赖机器 ID
- 对 ID 生成的性能与 ID 所占储存空间有一定要求
- 要求 ID 具有一定的有序性以提高数据库查询效率
Redis/ZooKeeper 生成全局 ID 适用场景:
- 中心化,复用中间件
- 要求保证 ID 的全局顺序性
- 在性能上能接受网络开销