ID生成器詳解


概述

ID 生成器也叫發號器,它的主要目的就是“為一個分布式系統的數據object產生一個唯一的標識”,但其實在一個真實的系統里可能也可以承擔更多的作用。概括起來主要有以下幾點:

唯一性
時間相關
粗略有序
可反解
可制造

要唯一性,是否需要全局唯一?

說起全局唯一,通常大家都會在想到發號器服務,分布式的通常需要更大空間,中心式的則需要在一個合適的地方在會聚。這就可能涉及到鎖,而鎖意味着成本和性能的下降。所以當前的系統是否需要全局的唯一性,就是一個需要考慮的問題。比如在通訊系統里,聊天消息可能就未必需要全局,因為一條消息只是某一個人發出,系統只要保證一個人維度的唯一性即可。本質上而言,這里利用了用戶 ID 的唯一性,因為唯一性是可以依賴的,通常我們設計系統也都是基於類似的性質,比如后面降到的使用時間唯一性的方式。

用時間來做什么?千萬年太久,只爭朝夕?

前面說到唯一性可以依賴,我們需要選擇的是依賴什么。通常的做法可以選擇數據庫自增,這在很多數據庫里都是可以滿足ACID 的操作。但是用數據庫有個缺點,就是數據庫有性能問題,在多機房情況下也很難處理。當然,你可以通過調整自增的步長來設計,但對於一個發號器而言,操作和維護都略重了。

而時間是天然唯一的,因此也是很多設計的選擇。但對於一個8Byte的 ID 而言,時間並沒有那么多。你如果精確到秒級別,三十年都要使用30bit,到毫秒級則要再增加10bit,你也只剩下20bit 可以做其他事情了。之所以在8Byte上搗鼓,因為8Byte 是一個Long,不管在處理器和編譯器還是語言層面,都是可以更好地被處理。

然而三十年夠么?對於一個人來說,可能不夠,但對一個系統而言,可能足夠。我們經常開玩笑,互聯網里能活三十年的系統有多少呢?三十年過去,你的系統可能都被重寫 N 遍了。這樣的信心同樣來自於摩爾定律,三十年后,計算性能早就提高了上千倍,到時候更多Byte 都不會是問題了。

粗略有多粗略,秒還是毫秒?

每秒一個或者每毫秒一個ID明顯是不夠的,剛才說到還有20bit 可以做其他事情,就包括一個SequenceID。如果要達到精確的有序,就要對 Sequence 進行並發控制,性能上肯定會打折。所以經常會有的一個選擇就是,在這個秒的級別上不再保證順序,而整個 ID 則只保證時間上的有序。后一秒的 ID肯定比前一秒的大,但同一秒內可能后取的ID比前面的號小。

那時間用秒還是毫秒呢?其實不用毫秒的時候就可以把空出來的10bit 送給 Sequence,但整個ID 的精度就下降了。峰值速度是更現實的考慮。Sequence 的空間決定了峰值的速度,而峰值也就意味着持續的時間不會太久。這方面,每秒100萬比每毫秒1000限制更小。

可反解,解開的是什么?

一個ID生成之后,就會伴隨着信息終身,排錯分析的時候,我們需要查驗。這時候一個可反解的 ID 可以幫上很多忙,從哪里來的,什么時候出生的。 跟身份證倒有點兒相通了,其實身份證就是一個典型的分布式 ID 生成器。

如果ID里已經有了時間而且能解開,在存儲層面可能不再需要timestamp一類的字段了。

可制造,為什么不用UUID?

互聯網系統上可用性永遠是優先指標。但由於分布式系統的脆弱,網絡不穩定或者底層存儲系統的不可用,業務系統隨時面臨着失敗。為了給前端更友好的響應,我們需要能盡量容忍失敗。比如在存儲失敗時,可能需要臨時導出請求供后續處理,而后續處理時已經離開了當時的時間點,順序跟其他系統錯開了。我們需要制造出這樣的ID以便系統好像一直正常運行一樣,可制造的ID 讓你可以控制生產日期,然后繼續下面的處理。

另一個重要場景就是數據清洗。這個屬於較少遇到,但並不罕見的情況,可能是原來ID設計的不合理,也可能由於底層存儲的改變,都可能出現。這樣一個可制造的 ID 就會帶來很多操作層面的便利。

這也是我們不用UUID的一個原因。UUID標准可以保證在某時某地生成,但如果要控制生成一個特定時間的 UUID,可能需要底層庫的改動。經驗告訴我們,能在上層解決的問題不要透到下層,這種庫的維護成本是非常高的。

其他

生成ID需考慮的元素

時間,可根據需要選擇秒級或者毫秒級。
順序號,當在時間等其他所有構成元素都相同的情況下,用於保證ID唯一性。
機器ID,可用MAC地址或者IP地址作為源,進行轉換得到。
進程ID,滿足一台機器上運行多個ID生成器的情況。

UUID

通用唯一識別碼(Universally Unique Identifier,簡稱UUID)是一種軟件建構的標准,亦為開放軟件基金會組織在分布式計算環境領域的一部分。
UUID的目的,是讓分布式系統中的所有元素,都能有唯一的辨識信息,而不需要通過中央控制端來做辨識信息的指定。如此一來,每個人都可以創建不與其它人沖突的UUID。在這樣的情況下,就不需考慮數據庫創建時的名稱重復問題。目前最廣泛應用的UUID,是微軟公司的全局唯一標識符(GUID),而其他重要的應用,則有Linux ext2/ext3文件系統、LUKS加密分區、GNOME、KDE、Mac OS X等等。另外我們也可以在e2fsprogs包中的UUID庫找到實現。

UUID是由一組32位數的16進制數字所構成,是故UUID理論上的總數為1632=2128,約等於3.4 x 1038。也就是說若每納秒產生1兆個UUID,要花100億年才會將所有UUID用完。UUID的標准型式包含32個16進制數字,以連字號分為五段,形式為8-4-4-4-12的32個字元。示例:

550e8400-e29b-41d4-a716-446655440000
隨機的UUID的重復概率

每秒產生10億筆UUID,100年后只產生一次重復的概率是50%。產生重復UUID的概率非常低,是故大可不必考慮此問題。

生日問題

假設有n個人在同一房間內,如果要計算有兩個人在同一日出生的概率,在不考慮特殊因素的前提下,例如閏年、雙胞胎,假設一年365日出生概率是平均分布的(現實生活中,出生概率不是平均分布的)。
計算概率的方法是,首先找出p(n)表示n個人中,每個人的生日日期都不同的概率。假如n > 365,根據鴿巢原理其概率為1,假設n ≤ 365,則概率為:

因為第二個人不能跟第一個人有相同的生日(概率是364/365),第三個人不能跟前兩個人生日相同(概率為363/365),依此類推。用階乘可以寫成如下形式:

p(n)表示n個人中至少2人生日相同的概率:

n≤365,根據鴿巢原理,n大於365時概率為1。

當n=23發生的概率大約是0.507。其他數字的概率用上面的算法可以近似的得出來:

n	p(n)
10	12%
20	41%
30	70%
50	97%
100	99.99996%
200	99.9999999999999999999999999998%
300	1 −(7×10−73)
350	1 −(3×10−131)
≥366	100%


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM