UniqueID

Swift 中 UUIDv4 和 v6* 的生成。

[文档]

UUID 是一个在空间和时间上都唯一的标识符,相对于所有 UUID 的空间而言。它们用于多种目的,从用极短的生命周期标记对象,到在网络上可靠地识别非常持久的对象。

UniqueID 支持任何 128 位 UUID,并且完全兼容 Foundation 的 UUID。它还包括生成两种 ID 的功能:

提示:随机和时间排序的 UUID 可以共存于同一个数据库中。它们具有不同的版本号,因此保证永远不会发生冲突。

在您的项目中使用 UniqueID

要在 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

为什么要使用新的 UUID?

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 仍然基于具有高熵的随机位分配。很容易认为您可能会为易用性付出高昂的冲突代价,但事实并非如此简单。