使用 Hypersistence TSID 生成按时间排序的唯一标识符
[LOADING...]
1. 简介
在本教程中,我们将探讨按时间排序的唯一标识符(TSID)。特别是,我们将研究 Hypersistence TSID 库,以便在 Java 中使用这些 ID。
2. 什么是 TSID
TSID(即按时间排序的唯一标识符)是指我们可以保证其唯一性,并且可以根据其生成时间进行自然排序的标识符。 此外,我们可以从这些 ID 中提取值,以确定生成每个 ID 的确切时间。
Hypersistence TSID 库允许我们将这些生成的 ID 表示为 64 位整数或 Base-32 编码 的 13 字符字符串。这使我们能够以针对机器处理或人类可读性进行优化的格式来存储或传输 ID。
2.1. TSID 结构
Hypersistence TSID 将值存储为 64 位整数。 它由三部分组成:
- 自纪元以来的毫秒数。这部分始终为 42 位。
- 节点 ID。我们可以将其配置为 0 到 20 位之间的大小。
- 计数器。这部分大小在 2 到 22 位之间,具体取决于节点 ID 的大小。
默认情况下,节点 ID 的长度为 10 位。这意味着计数器的长度为 12 位。
计数器部分的存在是为了确保即使在同一毫秒内也能生成唯一的值。 12 位的默认大小意味着我们每毫秒可以生成 2^12 = 4,096 个唯一值。将其增加到 22 位将允许我们每毫秒生成多达 4,194,304 个唯一值。
节点 ID 部分的作用是标识生成该 ID 的具体节点。 这使我们能够在不同服务器之间生成唯一的 ID,而无需任何复杂的协调。我们只需为每台服务器分配一个不同的节点 ID,这就可以保证在不同节点上生成的值永远不会发生冲突。
例如,我们可能有一个 ID 值 38,352,658,567,418,867。其分解如下:
- Timestamp = 1,692,990,591,987。这代表 UTC 时间 2023 年 8 月 25 日星期五 19:09:51.987。
- Node ID = 528
- Counter = 8
由该节点在同一毫秒内生成的下一个 ID 的计数器值将为 9。而由不同节点生成的 ID 将具有不同的节点 ID 值。因此,所有这些 ID 都是安全唯一的。
3. 依赖项
在使用 Hypersistence TSID 之前,我们需要在构建中包含最新版本,在撰写本文时为 2.1.4。
如果我们使用 Maven,可以在 pom.xml 文件中包含此依赖项:
此时,我们已准备好在应用程序中开始使用它。
4. 生成 TSID
一旦我们有了 Hypersistence TSID,就可以使用它来生成我们的 ID。
通常,我们将使用工厂来创建 TSID。 我们可以通过使用提供的单例实例来快速开始:
或者,我们可以创建自己的实例:
稍后我们将看到如何根据需要对其进行自定义。
我们的 TSID 工厂是线程安全的。因此,我们应该为每个节点创建一个实例,并在需要生成新 ID 的任何地方重用它,以确保它们始终是唯一的。
一旦我们有了 TSID 工厂,就可以使用 generate() 方法生成 TSID:
这将始终返回下一个唯一的 TSID,并遵循当前系统时间、当前计数器值和配置的节点值。
我们的 TSID 实例正确实现了 equals() 和 hashCode(),并且也是 Comparable(可比较的),因此我们可以安全地在任何需要的上下文中使用它们。
如果我们使用单例工厂,还有一个简写形式可以使用:
这与上面完全相同,只是写起来稍微短一点。
或者,我们有一个非常高效的获取 TSID 的简单选项:
这优先考虑速度,因此在生成过程中会走一些捷径。特别是,它忽略了生成 ID 的节点的设置,并且计数器只是无限递增,而不会在时间改变时重置。但是,如果性能比准确性更重要,那么这是一个不错的选择。
4.1. TSID 工厂
我们已经了解了如何创建用于生成新 TSID 的 TSID 工厂。执行此操作时,我们有几种方法可以对其进行配置以更好地满足我们的需求。
最有用的是,我们可以指定生成 ID 的节点部分的值。我们可以通过多种方式指定它:
- 我们工厂构建器上的
withNode()方法。 - 系统属性
tsid.node。 - 环境变量
TSID_NODE。 - 使用随机数。
这让我们可以完全控制使用哪个值,同时仍然确保始终提供一个值。
我们可以以类似的方式设置节点使用的位数:
- 我们工厂构建器上的
withNodeBits()方法。 - 系统属性
tsid.node.count。 - 环境变量
TSID_NODE_COUNT。 - 默认值 "10"。
通过系统属性和环境变量设置这些值,我们可以轻松地在应用程序的不同实例中更改它们,有助于确保生成的 ID 保持唯一。
除了指定节点之外,我们还可以自定义时间戳部分的生成。我们可以提供额外的时钟而不是使用系统时钟,甚至可以指定用于纪元的值而不是默认值:
在这里,我们提供了一个自定义时钟,并将纪元设置为 2000 年 1 月 1 日,而不是 1970 年。
5. 序列化 TSID
一旦我们生成了 TSID 实例,就需要能够对其进行序列化和反序列化。
TSID 类支持将生成的 ID 转换为 Long(如上所述的结构)或 String:
两者代表相同的 ID,只是格式不同。我们可以根据情况使用它们。例如,长整数格式更节省内存且更易于处理,但在某些语言中可能会有问题(例如,JavaScript 对数字使用浮点格式,因此对于这种大小的数字可能会有精度问题)。
我们还可以使用 TSID.from() 将这些字符串和长整数格式转换回 TSID 实例:
这些生成的值与系统最初生成的值完全相同。这意味着我们可以按照自己的意愿存储或传输 ID,并将它们恢复到原始状态。
如果需要,我们还可以提取 TSID 的时间戳部分。 这可以是 Instant 或自纪元以来的毫秒数:
我们无法访问节点或计数器值。我们需要知道分配给每个部分的位数才能获得此信息,而 TSID 不存储该信息。
6. 总结
在本文中,我们研究了按时间排序的唯一标识符的概念,并使用 Hypersistence TSID 库在 Java 中使用它们。下次你需要生成唯一 ID 时,不妨试一试?
像往常一样,本文中的所有示例都可以在 GitHub 上找到。