Swift 中 UUIDv4 和 v6* 的生成。
[文档]
UUID 是一个在空间和时间上都唯一的标识符,相对于所有 UUID 的空间而言。它们用于多种目的,从用极短的生命周期标记对象,到在网络上可靠地识别非常持久的对象。
UniqueID
支持任何 128 位 UUID,并且完全兼容 Foundation 的 UUID
。它还包括生成两种 ID 的功能:
随机:如 RFC-4122(UUIDv4)中所定义。
一个 128 位标识符,由 122 位随机位组成。这些是最常见的 UUID 形式;例如,它们是 Foundation 的 UUID
类型默认创建的。要生成一个随机 UUID,调用静态 random()
函数。
for _ in 0..<3 {
print(UniqueID.random())
}
"DFFC75B4-C92F-4DA9-97CA-7F0EEF067FF2"
"67E5F28C-5083-4908-BD69-D7E27C8BABA4"
"3BA8EEF0-DFBE-4AE0-A646-E165FCA9054C"
时间排序:根据 RFC-4122 的草案更新(UUIDv6)生成。
一个 128 位标识符,由一个 60 位时间戳(精度为 100 纳秒)、一个从随机位播种的 14 位序列号和一个 48 位节点 ID(也可能是随机位)组成。要生成一个时间排序的 UUID,调用静态 timeOrdered()
函数。
for _ in 0..<3 {
print(UniqueID.timeOrdered())
}
"1EC3C81E-A361-658C-BB38-65AAEF71CFCF"
"1EC3C81E-A361-6F6E-BB38-6DE69B9BCA1B"
"1EC3C81E-A362-698C-BB38-050642A95C73"
|------------- --| |--| |----------|
timestamp sq node
正如您所看到的,按顺序生成的时间排序 UUID 共享一个公共前缀(来自时间戳),但仍保持高碰撞避免率。这允许使用排序的数据结构和算法,例如二分查找,作为哈希表的替代方案。对于用作数据库主键,它们比随机 UUID 效率更高。
提示:随机和时间排序的 UUID 可以共存于同一个数据库中。它们具有不同的版本号,因此保证永远不会发生冲突。
要在 SwiftPM 项目中使用此包,您需要将其设置为包依赖项
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
.package(
url: "https://github.com/karwa/uniqueid",
.upToNextMajor(from: "1.0.0")
)
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "UniqueID", package: "uniqueid")
]
)
]
)
这样,您就可以开始使用 UniqueID
了。一种轻松试验时间排序 (v6) UUID 的方法是使用 Foundation 兼容性来简单地更改创建 UUID 的方式
import Foundation
import UniqueID
// Change from UUID() to UUID(.timeOrdered()).
struct MyRecord {
var id: UUID = UUID(.timeOrdered())
var name: String
}
// Read the timestamp by converting to UniqueID.
let uniqueID = UniqueID(myRecord.id)
print(uniqueID.components(.timeOrdered)?.timestamp)
请记住,v6 UUID 尚未成为正式标准,并且布局可能会在成为批准的互联网标准之前发生更改。此实现与 2021 年 10 月 7 日的草案 02 一致。请在此处查看最新状态:here。
IETF 草案对为什么使用时间排序的 UUID 会有益处进行了非常好的总结。您应该阅读它 - 至少阅读 "背景" 部分。
自从最初创建 UUID 以来,很多事情都发生了变化。现代应用程序需要使用(并且许多已经实现)UUID 作为数据库主键。
使用 UUID 作为数据库键的动机主要源于应用程序的本质越来越分布式。在分布式系统中,使用顺序整数的简单“自动递增”方案效果不佳,因为跨网络同步这些数字所需的工作很容易成为负担。UUID 可用于在分布式系统中创建唯一且相对较短的值,而无需同步,这一事实使它们非常适合用作此类环境中的数据库键。
但是,RFC4122 UUID 的某些属性不太适合此任务。首先,大多数现有的 UUID 版本(例如 UUIDv4)的数据库索引局部性较差。这意味着连续创建的新值在索引中并不彼此靠近,因此需要在随机位置执行插入。这对用于此目的的常见结构(B 树及其变体)的负面性能影响可能是巨大的。因此,应按时间顺序插入新插入的值以解决此问题。
以前的时间排序 UUID(例如 RFC-4122 中的版本 1 UUID)以复杂的格式存储其时间戳,因此您不能只根据其字节对 UUID 进行排序,并获得按时间排序的 UUID 列表。版本 6 对此进行了改进。
让我们比较 10 个 UUIDv4 和 10 个 UUIDv6
for _ in 0..<10 {
print(UniqueID.random())
}
DFFC75B4-C92F-4DA9-97CA-7F0EEF067FF2
67E5F28C-5083-4908-BD69-D7E27C8BABA4
3BA8EEF0-DFBE-4AE0-A646-E165FCA9054C
DF92B4B0-F5EE-42E5-9577-A9FC373C71A4
A2F8DD26-D513-4AE6-9E5C-58363885CCB6
BB0B5841-2BC0-49E2-BC5C-362CC34D7225
B08AF1F7-E2D3-4175-913D-369140612FF5
A453FB62-DF71-436F-9AC1-0414793DFA16
485EEB84-A4BA-44FE-BE3B-AD90390B0523
8A9AE1FA-4104-442C-B459-8F682E77F2F4
for _ in 0..<10 {
print(UniqueID.timeOrdered())
}
1EC3C81E-A35C-69E2-BB38-EDDC5E7E5F5E
1EC3C81E-A361-658C-BB38-65AAEF71CFCF
1EC3C81E-A361-6F6E-BB38-6DE69B9BCA1B
1EC3C81E-A362-698C-BB38-050642A95C73
1EC3C81E-A363-6152-BB38-F105ED78927F
1EC3C81E-A363-6A94-BB38-4DAB2CAE46CD
1EC3C81E-A364-63D6-BB38-6114031916EF
1EC3C81E-A364-6D04-BB38-435A854C2E42
1EC3C81E-A365-66AA-BB38-03504FA2F6FE
1EC3C81E-A365-6F74-BB38-1F5AE9E10389
这两个列表都是唯一的,并且彼此之间是唯一的,但时间排序的列表自然地按照创建时间的顺序排列。我们甚至可以提取嵌入的时间戳 - 在本例中,它表示 UUID 是在 2021 年 11 月 3 日 08:42:01 UTC 创建的(理论上精确到 100 纳秒)。
时间和空间组件的结合意味着这些 UUID 仍然对冲突具有很强的鲁棒性 - 每 100 纳秒存在一个新的 60 位宇宙,并且该宇宙中的 ID 仍然基于具有高熵的随机位分配。很容易认为您可能会为易用性付出高昂的冲突代价,但事实并非如此简单。