如何在分布式系統中生成唯一的標識符:6個關鍵策略

超級歐派課程 2024-03-30 03:11:31

在分布式環境中,兩個節點可以同時分配標識符,挑戰在于確保這些標識符保持唯一,避免沖突並確保系統一致性。

想象一下,在一個分布式系統中,有兩個節點同時運行,它們都負責爲存儲在共享存儲中的對象生成標識符。

如何確保這些節點生成的標識符是唯一的,不會發生沖突?

有多種策略可滿足這些要求,每種策略都適用于特定的要求:

唯一性:生成的每個標識符在系統的所有節點中都是唯一的。可擴展性:系統應能夠以高速率生成標識符而不發生沖突。單調遞增性:(如果需要)標識符應隨時間遞增。

我了解到許多生成標識符的方法,讓我們一起更詳細地了解其中的幾種:

UUID(通用唯一標識符)。NanoID。序列號。ObjectID。Twitter Snowflake。Sonyflake(受到Snowflake啓發)。

每種方法都有其自身的優點和挑戰,在我們逐個討論它們時,我會分享我的思考。

每周接收洞察性文章並培養閱讀習慣:

1. UUID 或 GUID(128位)

當談到生成唯一標識符時,UUID(通用唯一標識符)是首先想到的。

UUID由32個十六進制字符組成。請記住,每個字符占4位。因此,總共是128位。當加上4個連字符時,你會看到36個字符:

6e965784–98ef-4ebf-b477–8bd14164aaa45fd6c336-48c4-4510-bfe5-f7928a83a3e20333be18-5ecc-4d7e-98d4-80cc362e4ade

有5種常見的UUID類型:

版本1 - 基于時間和MAC地址:此UUID使用計算機的MAC地址和當前時間。版本2 - DCE安全:類似于版本1,但包含其他信息,如POSIX UID或GID。版本3 - 基于名稱的MD5:它接受一個命名空間和一個字符串,然後使用MD5生成UUID。版本4 - 隨機性:每個字符都是隨機選擇的。版本5 - 基于名稱的SHA1:類似于版本3,但它使用SHA-1而不是MD5。… 你可能還考慮其他草案,如版本6 - 重新排序的時間和版本7 - Unix紀元時間等,這是最新提議中的一些。

我現在不會詳細介紹每個版本的細節。但如果你不確定選擇哪個版本,我發現版本4 - 隨機性是一個很好的起點。它簡單而有效。

“隨機和唯一?這怎麽可能?”

其魔力在于極低的碰撞幾率。

引用維基百科的數據,想象每秒生成10億個UUID,持續86年,才有50%的幾率得到一次匹配。

“爲什麽有人說UUID只有122位,而不是128位?”

當人們談論UUID時,通常指的是最常見的UUID版本:版本4(或"v4")。版本4的UUID使用隨機數生成,因此具有最高的唯一性。由于UUID的128位中的6位已經用于特定目的(其中4位告訴我們這是版本4(或"v4"),另外2位用于保留變體信息),因此實際可用于唯一標識的位數爲122位。

優點

簡單,無需進行初始設置或使用集中式系統來管理ID。分布式系統中的每個服務都可以推出自己的唯一ID,無需交流。

缺點

128位長度較長,不容易寫下或記住。UUID不會透露太多關于自身的信息。UUID不可排序(除了版本1和2)。2. NanoID(126位)

NanoID從UUID的概念中提取出一部分,使用了只包含64個字符的字母表(包括連字符和下劃線),從而將事情簡化了一些,但只需21個字符。

進行一下計算,每個NanoID字符占用6位,而不是UUID的4位,快速乘法後,我們可以得到NanoID的長度爲整齊的126位。

NUp3FRBx-27u1kf1rmOxnXytMg-01fzdSaHoKXnPMJ_4hP-0rh8pNbx6-Qw1pMl

“在數據庫中存儲NanoID和UUID有很大的區別嗎?”

嗯,如果將它們保存爲字符串,NanoID可能會更高效一些,因爲它比UUID短15個字符,但在二進制形式下,兩者之間的差異只有2位,這在大多數存儲情況下通常是一個細節。

優點

NanoID使用字符(A-Za-z0–9_-),對URL友好。只需21個字符,比UUID更緊湊,精確地減少了15個字符(盡管它是126位,而UUID是128位)。

缺點

NanoID是較新的技術,可能沒有像UUID那樣廣泛支持。3. 序列號

序列號或自增可能會讓人想起,因爲像PostgreSQL和MySQL這樣的數據庫通常使用這種方法。

在其核心中,有一個遞增的集中式計數器,但想象一下同時出現數百萬個請求的情況。這個中心點將成爲瓶頸和潛在的單點故障。

“那又怎樣?我們不能分散負載嗎?”

當然可以。每個節點可以擁有自己的ID生成器,按照遞增的方式生成ID:

節點A:10 20 30 40節點B:1 11 21 31 41節點C:2 12 22 32 42// 或者節點A:a_1 a_2 a_3 a_4節點B:b_1 b_2 b_3 b_4

但是,排序變得有點棘手。

​雖然系統是分布式的,但每個節點仍然可能成爲瓶頸。如果某個節點被無數個請求壓垮,它將一個接一個地處理它們。

優點

這是一種直接的方法,額外的好處是ID可以排序,非常適合中小型系統。

缺點

在突發的大量請求增加時性能不佳。在分散式系統中移除節點可能會使問題複雜化。分散模型的ID不遵循全局順序,使排序變得複雜。4. ObjectID (96 bits)

ObjectID是MongoDB爲唯一文檔ID提供的解決方案,這個由12字節組成的標識符通常存儲在文檔的“_id”字段中。如果您沒有自己設置它,MongoDB會自動爲您設置。

下面是構成ObjectID的內容:

時間戳(4字節):表示對象創建的時間,從Unix紀元開始計算(對于可能需要回顧的人來說,這是從1970年開始的時間戳)。隨機值(5字節):每個機器或進程都有自己的隨機值。計數器(3字節):給定機器的簡單遞增計數器。

“但是每個進程如何確保其隨機值是唯一的呢?”

由于有5字節,我們可以得到2⁴⁰個潛在值,考慮到機器或進程的數量有限,沖突非常罕見。

在表示ObjectID時,MongoDB采用十六進制,將這12字節(或96位)轉換爲24個字符。

6502b4ab cf09f864b0 0748586502b4ab cf09f864b0 0748596502b4ab cf09f864b0 07485a

對于熟悉Go語言的人來說,以下是其實現的一瞥:

var objectIDCounter = readRandomUint32()var processUnique = processUniqueBytes()func NewObjectIDFromTimestamp(timestamp time.Time) ObjectID { var b [12]byte binary.BigEndian.PutUint32(b[0:4], uint32(timestamp.Unix())) copy(b[4:9], processUnique[:]) putUint24(b[9:12], atomic.AddUint32(&objectIDCounter, 1)) return b}

優點

無需集中機構監督唯一性,確保全局順序。從字節大小上看,比UUID和NanoID更緊湊。使用ID進行排序很簡單,您可以輕松查看每個對象的創建時間。顯示創建項的特定進程或機器。由于其基于時間的結構,可以優雅地擴展,確保不會産生未來沖突。

缺點

盡管相對緊湊,但96位仍可被視爲較長。在與客戶端共享ObjectID時要小心,可能會泄露過多信息。5. Twitter Snowflake(64位)

Twitter常用的“Snowflake ID”系統是爲了高效生成其龐大用戶群的ID而開發的。

Snowflake ID實際上是一個64位整數,比MongoDB的ObjectID更緊湊。

符號位(1位):通常未使用,但可以保留用于特定功能。時間戳(41位):與ObjectID類似,表示數據創建時間(以毫秒爲單位),跨越了約70年的時間。數據中心ID(5位):標識物理數據中心的位置。使用5位,我們最多可以有2⁵ = 32個數據中心。機器/進程ID(5位):與創建數據的單個機器、服務或進程相關聯。序列號(12位):在每毫秒重置爲0的遞增計數器。

“等等。70年?所以從1970年開始,到2040年就結束了?”

沒錯!

許多Snowflake實現使用最近開始的自定義紀元,例如2010年11月4日01:42:54 UTC。至于它的優點,由于設計得當,這些優點非常明顯。

您可以查看Go語言中的實現,使用的是bwmarrin/snowflake​:

func (n *Node) Generate() ID{ n.mu.Lock() now := time.Since(n.epoch).Nanoseconds() / 1000000 if now == n.time { n.step = (n.step + 1) & n.stepMask if n.step == 0 { for now <= n.time { now = time.Since(n.epoch).Nanoseconds() / 1000000 } } } else { n.step = 0 } n.time = now r := ID((now)<<n.timeShift | (n.node << n.nodeShift) | (n.step), ) n.mu.Unlock() return r}

缺點

對于中型企業來說,可能過于複雜,特別是對于具有多個數據中心、毫秒級時間戳、序列重置等複雜設置。壽命有限,大約爲70年。

它具有一些某些人可能認爲過多的功能,但對于像Twitter這樣的巨頭來說,它正好合適。

6. Sonyflake(64位)

受到Snowflake的啓發,Sonyflake在位分配上進行了一些改動:

符號位(1位)時間戳(39位):Sonyflake以10毫秒爲單位運行,將持續覆蓋時間從Snowflake的約70年擴展到約174年。機器/進程ID(16位)序列號(8位):每10毫秒允許256個ID,這比Snowflake略慢,增加了高峰時段ID重疊的機會。​

根據其規格,Sonyflake似乎更適合中小型系統,對于極速度和規模不重要的情況。

掌握Golang精髓,釋放編程潛能!關注我的《Golang實用技巧》專欄,它將爲你揭秘生産環境最佳實踐,帶你探索高並發編程的實用教程。從分享實用的Golang小技巧到深入剖析實際應用場景,讓你成爲真正的Golang大師。無論你是初學者還是經驗豐富的開發者,這裏都有你所需要的靈感和知識。讓我們一同探索Golang的無限可能!

0 阅读:0

超級歐派課程

簡介:感謝大家的關注