具有超持久性的時間排序唯一識別碼 (TSID)
1. 引言
在本教程中,我們將探討時間排序唯一識別碼(TSID)。具體來說,我們將使用Hypersistence TSID 函式庫在 Java 中處理這些 ID。
2. 什麼是TSID?
TSID(時間排序唯一識別碼)是一種我們可以保證唯一且能依產生時間自然排序的識別碼。此外,我們還可以從這些 ID 中檢索值,以確定每個 ID 的確切產生時間。
Hypersistence TSID 函式庫允許我們將產生的 ID 表示為 64 位元整數或 Base32 編碼的 13 個字元的字串。這樣,我們就可以以針對機器處理或人類可讀性最佳化的格式儲存或傳輸 ID。
2.1. TSID結構
超持久性 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 的 ID。這可以分解為:
00000000 10001000 01000001 10001010 00101110 00011010 01010011 11110011
|-----------||---------||---------------------------------------------|
| Counter || Node ID || Timestamp |
- 時間戳 = 1,692,990,591,987。這表示 2023 年 8 月 25 日星期五 19:09:51.987 UTC。
- 節點 ID = 528
- 計數器 = 8
此節點在同一毫秒內產生的下一個 ID 的計數器值為 9。而不同節點產生的 ID 將具有不同的節點 ID 值。因此,所有這些 ID 都將是絕對唯一的。
3. 依賴關係
在使用 Hypersistence TSID 之前,我們需要將最新版本包含在我們的建置中,截至撰寫本文時,最新版本為2.1.4 。
如果我們使用 Maven,我們可以將此依賴項新增到 pom.xml 檔案中:
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-tsid</artifactId>
<version>2.1.4</version>
</dependency>
現在,我們已經準備好在我們的應用程式中使用它了。
4. 產生 TSID
一旦我們有了 Hypersistence TSID,我們就可以用它來產生我們的 ID 了。
通常情況下,我們會使用工廠模式來建立 TSID。我們可以透過使用提供的單例實例快速入門:
TSID.Factory tsidFactory = TSID.Factory.INSTANCE;
或者,我們可以創建自己的實例:
TSID.Factory tsidFactory = TSID.Factory.builder().build();
稍後我們會看看如何根據我們的需求進行客製化。
我們的 TSID 工廠是線程安全的。因此,我們應該為每個節點建立一個實例,並在需要產生新 ID 的所有地方重複使用該實例,以確保 ID 始終是唯一的。
一旦我們有了 TSID 工廠,我們就可以使用generate()來產生 TSID:
TSID tsid = tsidFactory.generate();
這將始終傳回下一個唯一的 TSID,同時考慮目前系統時間、目前計數器值和配置的節點值。
我們的 TSID 實例正確實作了equals()和hashCode() ,而且也是Comparable ,因此我們可以在任何需要的上下文中安全地使用它們。
如果我們使用單例工廠模式,我們也可以使用一種簡寫形式:
TSID tsid = TSID.Factory.getTsid();
這與上面的內容完全相同,只是寫起來稍微短一點。
或者,我們還有一種更簡單有效率的方式來取得TSID:
TSID tsid = TSID.fast();
這種方法優先考慮速度,因此在生成過程中會做出一些妥協。具體來說,它會忽略生成 ID 的節點的設置,並且計數器會無限遞增而不會在時間變化時重置。但是,如果性能比準確性更重要,那麼這是一個不錯的選擇。
4.1. TSID工廠
我們已經了解如何建立用於產生新 TSID 的 TSID 工廠。在創建 TSID 工廠時,我們可以透過多種方式對其進行配置,以更好地滿足我們的需求。
最實用的是,我們可以指定產生的 ID 中節點部分的值。我們可以透過多種方式指定:
- 我們工廠建構器中的
withNode()方法。 - 系統屬性
tsid.node。 - 環境變數
TSID_NODE。 - 使用隨機數。
這樣我們就可以完全控制要使用哪個值,同時也能確保總是提供一個值。
我們可以採用類似的方式來設定節點使用的位數:
- 我們工廠建構器中的
withNodeBits()方法。 - 系統屬性
tsid.node.count。 - 環境變數
TSID_NODE_COUNT。 - 預設值為“10”。
透過系統屬性和環境變數設定這些值,我們可以輕鬆地在應用程式的不同實例中更改它們,從而幫助確保生成的 ID 保持唯一性。
除了指定節點之外,我們還可以自訂時間戳部分的產生方式。我們可以提供一個額外的時鐘來取代系統時鐘,甚至可以指定紀元值而不是預設值:
TSID.Factory factory = TSID.Factory.builder()
.withClock(clock)
.withCustomEpoch(Instant.parse("2000-01-01T00:00:00Z"))
.build();
在這裡,我們提供了一個自訂時鐘,並將紀元設定為 2000 年 1 月 1 日,而不是 1970 年。
5. 序列化 TSID
一旦我們產生了TSID實例,我們就需要能夠對其進行序列化,並且可能需要對其進行反序列化。
TSID類別支援將產生的 ID 轉換為Long類型(結構如上所述)或String :
long tsidLong = tsid.toLong(); // 809100737063473402
String tsidString = tsid.toString(); // 0PEM0TNJ2SM7T
兩者都代表同一個 ID,只是格式不同。我們可以根據情況使用它們。例如,長整型格式更節省記憶體且更易於操作,但在某些語言中可能會出現問題(例如,JavaScript 使用浮點格式表示數字,因此在處理這種大小的數字時可能會出現精確度問題)。
我們也可以使用TSID.from()將這些字串和長格式轉換回TSID實例:
TSID tsid = TSID.from(tsidLong);
TSID tsid = TSID.from(tsidString);
這些操作會產生與系統最初產生的值完全相同的值。這意味著我們可以根據需要儲存或傳輸我們的 ID,並將其恢復到原始狀態。
如有需要,我們也可以提取 TSID 的時間戳記部分。時間戳記可以是Instant值,也可以是自紀元以來的毫秒數:
Instant instant = tsid.getInstant();
long millis = tsid.getUnixMilliseconds();
我們無法存取節點或計數器的值。我們需要知道每個節點或計數器分配了多少位才能獲得這些信息,而 TSID 並未儲存該資訊。
6. 總結
本文探討了時間排序唯一識別碼的概念,並使用 Hypersistence TSID 函式庫在 Java 中實現了這項功能。下次需要產生唯一 ID 時,不妨試試。
與往常一樣,本文中的所有範例都可以在 GitHub 上找到。