1. 程式人生 > >39、談談常用的分散式ID的設計方案?Snowflake是否受冬令時切換影響?

39、談談常用的分散式ID的設計方案?Snowflake是否受冬令時切換影響?

專欄的絕大部分主題都側重於 Java 語言和虛擬機器,基本都是單機模式下的問題,今天我會補充一個分散式相關的問題。嚴格來說,分散式並不算是 Java 領域,而是一個單獨的大主題,但確實也會在 Java 技術崗位面試中被涉及。在準備面試時,如果有豐富的分散式系統經驗當然好;如果沒有,你可以選擇典型問題和基礎技術進行適當準備。關於分散式,我自身的實戰經驗也非常有限,專欄裡就談談從理論出發的一些思考。

今天我要問你的問題是,談談常用的分散式 ID 的設計方案?Snowflake 是否受冬令時切換影響?

典型回答

首先,我們需要明確通常的分散式 ID 定義,基本的要求包括:

  •   全域性唯一,區別於單點系統的唯一,全域性是要求分散式系統內唯一。
  •   有序性,通常都需要保證生成的 ID 是有序遞增的。例如,在資料庫儲存等場景中,有序 ID 便於確定資料位置,往往更加高效。

目前業界的方案很多,典型方案包括:

  •   基於資料庫自增序列的實現。這種方式優缺點都非常明顯,好處是簡單易用,但是在擴充套件性和可靠性等方面存在侷限性。
  •   基於 Twitter 早期開源的Snowflake的實現,以及相關改動方案。這是目前應用相對比較廣泛的一種方式,其結構定義你可以參考下面的示意圖。


整體長度通常是 64 (1 + 41 + 10+ 12 = 64)位,適合使用 Java 語言中的 long 型別來儲存。
 
頭部是 1 位的正負標識位。

緊跟著的高位部分包含 41 位時間戳,通常使用 System.currentTimeMillis()。

後面是 10 位的 WorkerID,標準定義是 5 位資料中心 + 5 位機器 ID,組成了機器編號,以區分不同的叢集節點。

最後的 12 位就是單位毫秒內可生成的序列號數目的理論極限。

Snowflake 的官方版本是基於 Scala 語言,Java 等其他語言的參考實現有很多,是一種非常簡單實用的方式,具體位數的定義是可以根據分散式系統的真實場景進行修改的,並不一定要嚴格按照示意圖中的設計。

  •   Redis、Zookeeper、MangoDB 等中介軟體,也都有各種唯一 ID 解決方案。其中一些設計也可以算作是 Snowflake 方案的變種。例如,MongoDB 的ObjectId提供了一個 12 byte(96 位)的 ID 定義,其中 32 位用於記錄以秒為單位的時間,機器 ID 則為 24 位,16 位用作程序 ID,24 位隨機起始的計數序列。
  •   國內的一些大廠開源了其自身的部分分散式 ID 實現,InfoQ  就曾經介紹過微信的seqsvr,它採取了相對複雜的兩層架構,並根據社交應用的資料特點進行了針對性設計,具體請參考相關程式碼實現。另外,百度、美團等也都有開源或者分享了不同的分散式理ID 實現,都可以進行參考。


關於第二個問題,Snowflake 是否受冬令時切換影響?

我認為沒有影響,你可以從 Snowflake 的具體演算法實現尋找答案。我們知道 Snowflake 演算法的 Java 實現,大都是依賴於 System.currentTimeMillis(),這個數值代表什麼呢?從 Javadoc 可以看出,它是返回當前時間和 1970 年 1 月 1 號 UTC 時間相差的毫秒數,這個數值與夏 / 冬令時並沒有關係,所以並不受其影響。

 

考點分析

今天的問題不僅源自面試的熱門考點,並且也存在著廣泛的應用場景,我前面給出的回答只是一個比較精簡的典型方案介紹。我建議你針對特定的方案進行深入分析,以保證在面試官可能會深入追問時能有充分準備;如果恰好在現有系統使用分散式 
ID,理解其設計細節是很有必要的。

涉及分散式,很多單機模式下的簡單問題突然就變得複雜了,這是分散式天然的複雜性,需要從不同角度去理解適用場景、架構和細節演算法,我會從下面的角度進行適當解讀:

  •   我們的業務到底需要什麼樣的分散式 ID,除了唯一和有序,還有哪些必須要考慮的要素?
  •   在實際場景中,針對典型的方案,有哪些可能的侷限性或者問題,可以採取什麼辦法解決呢?

 

知識擴充套件

如果試圖深入回答這個問題,首先需要明確業務場景的需求要點,我們到底需要一個什麼樣的分散式 ID?

除了唯一和有序,考慮到分散式系統的功能需要,通常還會額外希望分散式 ID 保證:

  •   有意義,或者說包含更多資訊,例如時間、業務等資訊。這一點和有序性要求存在一定關聯,如果 ID 中包含時間,本身就能保證一定程度的有序,雖然並不能絕對保證。ID 中包含額外資訊,在分散式資料儲存等場合中,有助於進一步優化資料訪問的效率。
  •   高可用性,這是分散式系統的必然要求。前面談到的方案中,有的是真正意義上的分散式,有得還是傳統主從的思路,這一點沒有絕對的對錯,取決於我們業務對擴充套件性、效能等方面的要求。
  •   緊湊性,ID 的大小可能受到實際應用的制約,例如資料庫儲存往往對長 ID 不友好,太長的 ID 會降低 MySQL 等資料庫索引的效能;程式語言在處理時也可能受資料型別長度限制。

在具體的生產環境中,還有可能提出對 QPS 等方面的具體要求,尤其是在國內一線網際網路公司的業務規模下,更是需要考慮峰值業務場景的數量級層次需求。

 

第二,主流方案的優缺點分析。

對於資料庫自增方案,除了實現簡單,它生成的 ID 還能夠保證固定步長的遞增,使用很方便。

但是,因為每獲取一個 ID 就會觸發資料庫的寫請求,是一個代價高昂的操作,構建高擴充套件性、高效能解決方案比較複雜,效能上限明顯,更不要談擴容等場景的難度了。與此同時,保證資料庫方案的高可用性也存在挑戰,資料庫可能發生宕機,即使採取主從熱備等各種措施,也可能出現時ID 重複等問題。

實際大廠商往往是構建了多層的複合架構,例如美團公開的資料庫方案Leaf-Segment,引入了起到快取等作用的 Leaf 
層,對資料庫操作則是通過資料庫中介軟體提供的批量操作,這樣既能保證效能、擴充套件性,也能保證高可用。但是,這種方案對基礎架構層面的要求很多,未必適合普通業務規模的需求。

與其相比,Snowflake 方案的好處是演算法簡單,依賴也非常少,生成的序列可預測,效能也非常好,比如 Twitter 的峰值超過 10 萬 /s。

但是,它也存在一定的不足,例如:

  •   時鐘偏斜問題(Clock Skew)。我們知道普通的計算機系統時鐘並不能保證長久的一致性,可能發生時鐘回撥等問題,這就會導致時間戳不準確,進而產生重複 ID。

針對這一點,Twitter 曾經在文件中建議開啟NTP,畢竟 Snowflake 對時間存在依賴,但是也有人提議關閉 NTP。我個人認為還是應該開啟 NTP,只是可以考慮將 stepback 設定為 0,以禁止回撥。

從設計和具體編碼的角度,還有一個很有效的措施就是快取歷史時間戳,然後在序列生成之前進行檢驗,如果出現當前時間落後於歷史時間的不合理情況,可以採取相應的動作,要麼重試、等待時鐘重新一致,或者就直接提示服務不可用。

  •   另外,序列號的可預測性是把雙刃劍,雖然簡化了一些工程問題,但很多業務場景並不適合可預測的 ID。如果你用它作為安全令牌之類,則是非常危險的,很容易被黑客猜測並利用。
  •   ID 設計階段需要謹慎考慮暴露出的資訊。例如,Erlang 版本的 flake 實現基於 MAC 地址計算  WorkerID,在安全敏感的領域往往是不可以這樣使用的。
  •   從理論上來說,類似 Snowflake 的方案由於時間資料位數的限制,存在與2038 年問題相似的理論極限。雖然目前的系統設計考慮數十年後的問題還太早,但是理解這些可能的極限是有必要的,也許會成為面試的過程中的考察點。


如果更加深入到時鐘和分散式系統時序的問題,還有與分散式 ID 
相關但又有所區別的問題,比如在分散式系統中,不同機器的時間很可能是不一致的,如何保證事件的有序性?Lamport 在 1978 年的論文(Time, 
Clocks, and the Ording of Events in a Distributed 
System)中就有很深入的闡述,有興趣的同學可以去查詢相應的翻譯和解讀。

最後,我再補充一些當前分散式領域的面試熱點,例如:

  •   分散式事務,包括其產生原因、業務背景、主流的解決方案等。
  •   理解CAP、BASE等理論,懂得從最終一致性等角度來思考問題,理解Paxos、Raft等一致性演算法。
  •   理解典型的分散式鎖實現,例如最常見的Redis 分散式鎖。
  •   負載均衡等分散式領域的典型演算法,至少要了解主要方案的原理。


這些方面目前都已經有相對比較深入的分析,尤其是來自於一線大廠的實踐經驗。另外,在左耳聽風專欄的“程式設計師練級攻略”裡,提供了非常全面的分散式學習資料,感興趣的同學可以參考。

今天我簡要梳理了當前典型的分散式 ID 生成方案,並探討了 ID 設計的一些考量,尤其是應用相對廣泛的 Snowflake 的不足之處,希望對你有所幫助。

 

一課一練

關於今天我們討論的題目你做到心中有數了嗎?今天的思考題是,從理論上來看,Snowflake 這種基於時間的演算法,從形式上天然地限制了 ID 的併發生成數量,如果在極端情況下,短時間需要更多 ID,有什麼辦法解決呢?