標籤:
ID 產生器在微博我們一直叫發號器,微博就是用這樣的號來儲存,而我微博裡討論的時候也都是以發號器為標籤。它的主要目的確如平常大家理解的“為一個分布式系統的資料object產生一個唯一的標識”,但其實在一個真實的系統裡可能也可以承擔更多的作用。概括起來主要有以下幾點:
1. 唯一性2. 時間相關
3. 粗略有序4. 可反解5. 可製造
下面我會分別講每個作用後面的考慮和權衡,也會對比介紹一下業界已知的幾種 ID 設計。
1. 要唯一性,是否需要全域唯一?
說起全域唯一,通常大家都會在想到發號器服務,分布式的通常需要更大空間,中心式的則需要在一個合適的地方在會聚。這就可能涉及到鎖,而鎖意味著成本和效能的下降。所以當前的系統是否需要全域的唯一性,就是一個需要考慮的問題。
比如在通訊系統裡,聊天訊息可能就未必需要全域,因為一條訊息只是某一個人發出,系統只要保證一個人維度唯一性即可。本質上而言,這裡利用了使用者識別碼 的唯一性,因為唯一性是可以依賴的,通常我們設計系統也都是基於類似的性質,比如後面降到的使用時間唯一性的方式。
2. 用時間來做什嗎?千萬年太久,只爭朝夕?
前面說到唯一性可以依賴,我們需要選擇的是依賴什麼。通常的做法可以選擇資料庫自增,這在很多資料庫裡都是可以滿足ACID 的操作。但是用資料庫有個缺點,就是資料庫有效能問題,在多機房情況下也很難處理。當然,你可以通過調整自增的步長來設計,但對於一個發號器而言,操作和維護都略重了。
而時間是天然唯一的,因此也是很多設計的選擇。但對於一個8Byte的 ID 而言,時間並沒有那麼多。你如果精確到秒層級,三十年都要使用30bit,到毫秒級則要再增加10bit,你也只剩下20bit 可以做其他事情了。之所以在8Byte 上搗鼓,因為8Byte 是一個Long,不管在處理器和編譯器還是語言層面,都是可以更好地被處理。
然而三十年夠嗎?對於一個人來說,可能不夠,但對一個系統而言,可能足夠。我們經常開玩笑,互連網裡能活三十年的系統有多少呢?三十年過去,你的系統可能都被重寫 N 遍了。這樣的信心同樣來自於摩爾定律,三十年後,計算效能早就提高了上千倍,到時候更多Byte 都不會是問題了。
3. 粗略有多粗略,秒還是毫秒?
每秒一個或者每毫秒一個ID明顯是不夠的,剛才說到還有20bit 可以做其他事情,就包括一個SequenceID。如果要達到精確的有序,就要對 Sequence 進行並發控制,效能上肯定會打折。所以經常會有的一個選擇就是,在這個秒的層級上不再保證順序,而整個 ID 則只保證時間上的有序。後一秒的 ID肯定比前一秒的大,但同一秒內可能後取的ID比前面的號小。這在使用時非常關鍵,你要理解,系統也要接受才可以。
那時間用秒還是毫秒呢?其實不用毫秒的時候就可以把空出來的10bit 送給 Sequence,但整個ID 的精度就下降了。峰值速度是更現實的考慮。Sequence 的空間決定了峰值的速度,而峰值也就意味著持續的時間不會太久。這方面,每秒100萬比每毫秒1000限制更小。
4. 可反解,解開的是什嗎?
一個 ID 產生之後,就會伴隨著資訊終身,排錯分析的時候,我們需要查驗。這時候一個可反解的 ID 可以幫上很多忙,從哪裡來的,什麼時候出生的。 跟身份證倒有點兒相通了,其實身份證就是一個典型的分布式 ID 產生器。
如果ID 裡已經有了時間而且能解開,在儲存層面可能不再需要timestamp 一類的欄位了。微博的 ID 還有很多商務資訊,這個後面會細講。
5. 可製造,為什麼不用UUID?
互連網系統上可用性永遠是優先指標。但由於分布式系統的脆弱,網路不穩定或者底層儲存系統的不可用,業務系統隨時面臨著失敗。為了給前端更友好的響應,我們需要能盡量容忍失敗。比如在儲存失敗時,可能需要臨時匯出請求供後續處理,而後續處理時已經離開了當時的時間點,順序跟其他系統錯開了。我們需要製造出這樣的ID 以便系統好像一直正常運行一樣,可製造的 ID 讓你可以控制生產日期(汗,有點兒假冒偽劣的意思了),然後繼續下面的處理。
另一個重要情境就是資料清洗。這個屬於較少遇到,但並不罕見的情況,可能是原來 ID 設計的不合理,也可能由於底層儲存的改變,都可能出現。這樣一個可製造的 ID 就會帶來很多操作層面的便利。
這也是我們不用 UUID 的一個原因。UUID 標準可以保證在某時某地產生,但如果要控制產生一個特定時間的 UUID,可能需要底層庫的改動。經驗告訴我們,能在上層解決的問題不要透到下層,這種庫的維護成本是非常高的。
#設計細節
UUID 就不說了, 其他公開出來的這裡說下SnowFlake、Weibo以及 Ticktick 的設計。
1. SnowFlake
41bit留給毫秒時間,10bit給MachineID,也就是機器要預先配置,剩下12位留給Sequence。代碼雖然露出來了,但其實已經不可用了,據說是內部改造中。
2. Weibo
微博使用了秒級的時間,用了30bit,Sequence 用了15位,理論上可以搞定3.2w/s的速度。用4bit來區分IDC,也就是可以支援16個 IDC,對於核心機房來說夠了。剩下的有2bit 用來區分業務,由於當前發號服務是機房中心式的,1bit 來區分熱備。是的,也沒有用滿64bit。
3. 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
業務系統需要怎樣的全域唯一ID? #Ticktick#(環信首席架構師:一樂)