1. 概述
UUID 有不同的版本实现,通常我们说的 UUID 一般指的是UUID V4。本文主要讨论基于时间的UUID V1,以及最新的UUID v6 和v7版本。
我们将讨论基于时间的 UUID 的优点和缺点,以及何时应该选择它们。
最后我们学习如何在 Java 中使用第三方依赖库生成这些 UUID。
2. UUID 和基于时间的 UUID
UUID 是Universally Unique Identifier(通用唯一识别码)的缩写。它是一个 128 位标识符,每次生成时都应是唯一的。
UUID 不依赖中央机构的注册和分配,不分语言和平台,重复的概率接近零,可以忽略不计。
UUID 有不同的实现版本,其中UUID v1是基于时间 + 机器码(通常为MAC地址)生成的。最新的UUID V6、UUID v7 也是基于时间生成的,后面我们会讨论。
v1 版本具有几个优点
- 按时间排序的 ID 更适合作为数据库的主键
- 包含创建时间戳有助于分析和调试。
v1 版本缺点
- 从同一主机生成多个 ID 时发生冲突的可能性略高。我们稍后会看到这是否是一个问题。
- MAC 地址的暴露会造成了隐私与安全问题,这就是UUID v6试图提高安全性的原因。
3. 基准测试
为了更直观的对比说明,下面我们编写一个测试程序来比较 UUID 发生重复概率及 UUID 的生成耗时。
初始化变量:
int threadCount = 128;
int iterationCount = 100_000;
Map<UUID, Long> uuidMap = new ConcurrentHashMap<>();
AtomicLong collisionCount = new AtomicLong();
long startNanos = System.nanoTime();
CountDownLatch endLatch = new CountDownLatch(threadCount);
我们启动 128 个线程,每个线程进行 100000 次迭代,使用 ConcurentHashMap 保存生成的 UUID。collisionCount 记录UUID冲突的次数。
for (long i = 0; i < threadCount; i++) {
long threadId = i;
new Thread(() -> {
for (long j = 0; j < iterationCount; j++) {
UUID uuid = UUID.randomUUID();
Long existingUUID = uuidMap.put(uuid, (threadId * iterationCount) + j);
if (existingUUID != null) {
collisionCount.incrementAndGet();
}
}
endLatch.countDown();
}).start();
}
使用 CountDownLatch await() 方法等待所有线程执行完毕。
endLatch.await();
System.out.println(threadCount * iterationCount + " UUIDs generated, " + collisionCount + " collisions in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos) + "ms");
下面是使用 JDK 内置的 UUID 生成器测试结果:
12800000 UUIDs generated, 0 collisions in 4622ms
4622毫秒,生成1280万 UUID,0冲突。下面我们将与第三方库进行比较
4. 使用 UUID Creator
4.1. Maven 依赖
下面我们使用 UUID Creator 库生成UUID,导入maven 依赖:
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>uuid-creator</artifactId>
<version>5.2.0</version>
</dependency>
4.2. 使用方法
提供三种方式生成基于时间的 UUID,分别对应 UUID v1, v6 和v7:
- UuidCreator.getTimeBased() – v1 版本
- UuidCreator.getTimeOrdered() – v6 版本
- UuidCreator.getTimeOrderedEpoch() – v7 版本
使用方法:
System.out.println("UUID Version 1: " + UuidCreator.getTimeBased());
System.out.println("UUID Version 6: " + UuidCreator.getTimeOrdered());
System.out.println("UUID Version 7: " + UuidCreator.getTimeOrderedEpoch());
打印结果:
UUID Version 1: 0da151ed-c82d-11ed-a2f6-6748247d7506
UUID Version 6: 1edc82d0-da0e-654b-9a98-79d770c05a84
UUID Version 7: 01870603-f211-7b9a-a7ea-4a98f5320ff8
对于 getTimeBased(),即UUID v1 它由3部分组成:时间戳,时钟序列,节点标识
Time-based UUID structure
00000000-0000-v000-m000-000000000000
|1-----------------|2---|3-----------|
1: timestamp
2: clock-sequence
3: node identifier
4.3. 基准测试
使用 UuidCreator.getTimeBased() 做基准测试:
12800000 UUIDs generated, 0 collisions in 2595ms
可以看出效率要比 JDK 的高,详细benchmarks请参考官网。
5. 使用 JUG 库
5.1. Maven 依赖
下面我们使用另一个库: Java UUID Generator (JUG)
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>4.1.0</version>
</dependency>
5.2. 使用方法
该库同样它支持v1-v7版本:
System.out.println("UUID Version 1: " + Generators.timeBasedGenerator().generate());
System.out.println("UUID Version 6: " + Generators.timeBasedReorderedGenerator().generate());
System.out.println("UUID Version 7: " + Generators.timeBasedEpochGenerator().generate());
生成结果:
UUID Version 1: e6e3422c-c82d-11ed-8761-3ff799965458
UUID Version 6: 1edc82de-6e34-622d-8761-dffbc0ff00e8
UUID Version 7: 01870609-81e5-793b-9e4f-011ee370187b
5.3. 基准测试
性能测试
UUID uuid = Generators.timeBasedGenerator().generate();
运行结果:
12800000 UUIDs generated, 0 collisions in 15795ms
可以看到这3种方式都没有出现重复的UUID,速度也不是瓶颈,虽然最后一个库要慢一点。
6. 总结
本文我们讨论了基于时间UUID的优缺点,并学习如何使用Java库生成UUID v6 和UUID v7。
最后,本文的源码可在GitHub上获取。