業務系統需要什么樣的ID生成器
ID 生成器在微博我們一直叫發號器,微博就是用這樣的號來存儲,而我微博里討論的時候也都是以發號器為標簽。它的主要目的確如平常大家理解的“為一個分布式系 統的數據object產生一個唯一的標識”,但其實在一個真實的系統里可能也可以承擔更多的作用。概括起來主要有以下幾點:
- 唯一性
- 時間相關
- 粗略有序
- 可反解
- 可制造
下面我會分別講每個作用后面的考慮和權衡,也會對比介紹一下業界已知的幾種 ID 設計。
-
要唯一性,是否需要全局唯一?
說起全局唯一,通常大家都會在想到發號器服務,分布式的通常需要更大空間,中心式的則需要在一個合適的地方在會聚。這就可能涉及到鎖,而鎖意味著成本和性能的下降。所以當前的系統是否需要全局的唯一性,就是一個需要考慮的問題。
比如在通訊系統里,聊天消息可能就未必需要全局,因為一條消息只是某一個人發出,系統只要保證一個人維度的唯一性即可。本質上而言,這里利用了用戶 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 一類的字段了。微博的 ID 還有很多業務信息,這個后面會細講。
-
可制造,為什么不用UUID?
互聯網系統上可用性永遠是優先指標。但由于分布式系統的脆弱,網絡不穩定或者底層存儲系統的不可用,業務系統隨時面臨著失敗。為了給前端更友好的響應,我 們需要能盡量容忍失敗。比如在存儲失敗時,可能需要臨時導出請求供后續處理,而后續處理時已經離開了當時的時間點,順序跟其他系統錯開了。我們需要制造出 這樣的ID 以便系統好像一直正常運行一樣,可制造的 ID 讓你可以控制生產日期(汗,有點兒假冒偽劣的意思了),然后繼續下面的處理。
另一個重要場景就是數據清洗。這個屬于較少遇到,但并不罕見的情況,可能是原來 ID 設計的不合理,也可能由于底層存儲的改變,都可能出現。這樣一個可制造的 ID 就會帶來很多操作層面的便利。
這也是我們不用 UUID 的一個原因。UUID 標準可以保證在某時某地生成,但如果要控制生成一個特定時間的 UUID,可能需要底層庫的改動。經驗告訴我們,能在上層解決的問題不要透到下層,這種庫的維護成本是非常高的。
設計細節
UUID 就不說了, 其他公開出來的這里說下SnowFlake、Weibo以及 Ticktick 的設計。
-
SnowFlake
41bit留給毫秒時間,10bit給MachineID,也就是機器要預先配置,剩下12位留給Sequence。代碼雖然露出來了,但其實已經不可用了,據說是內部改造中。
-
Weibo
微博使用了秒級的時間,用了30bit,Sequence 用了15位,理論上可以搞定3.2w/s的速度。用4bit來區分IDC,也就是可以支持16個 IDC,對于核心機房來說夠了。剩下的有2bit 用來區分業務,由于當前發號服務是機房中心式的,1bit 來區分熱備。是的,也沒有用滿64bit。
-
Ticktick
也就是當前在環信系統里要用到的。使用了30bit 的秒級時間,20bit 給Sequence。這里是有個考慮,第一版實現還是希望到毫秒級,所以20bit 的前10bit給了毫秒來用,剩下10bit給 Sequence。等到峰值提高的時候可以暫時回到秒級。
前面說到的三十年問題,因此我在高位留了2bit 做 Version,或者到時候改造使用更長字節數,用第一位來標識不同 ID,或者可以把這2bit 挪給時間用,可以給系統改造留出一定的時間。
剩下的10bit 留給 MachineID,也就是說當前 ID 生成可以直接內嵌在業務服務中,最多支持千級別的服務器數量。最后有2bit 做Tag 用,可能區分群消息和單聊消息。同時你也看出,這個 ID 最多支持一天10億消息,也是怕系統增速太快,這2bit 可以挪給 Sequence,可以支持40億級別消息量,或者結合前面的版本支持到百億級別。
后記
自己實現一個發號器非常簡單,所以Ticktick 怎么實現并不重要。不過吶,我還是有 demo 源碼的,見 https://github.com/ericliang/ticktick
來自:http://ericliang.info/what-kind-of-id-generator-we-need-in-business-systems/