1. 程式人生 > >[分散式系統學習]閱讀筆記 Distributed systems for fun and profit 之三 時間和順序

[分散式系統學習]閱讀筆記 Distributed systems for fun and profit 之三 時間和順序

這是閱讀 http://book.mixu.net/distsys/time.html 的筆記,是該系列的第三章。

為什麼時間和順序很重要呢?為什麼我們關係事件A發生在事件B之前?

因為分散式系統要解決的問題是把單機上的問題通過多機來解決。然而傳統單機的程式總是假設確定的順序。對於分散式程式來說,正確性最簡單的定義就是,跑起來像一臺單機上執行的程式。

全序和偏序

具體的定義大家可以去翻離散書。簡單地說,全序就是在集合裡任何兩個元素都可以比較,分出大小。偏序中,某些元素是沒辦法比較大小的。

在單節點系統中,全序是必然的。因為單機上指令順序執行。程式執行可預測。這個性質在分散式系統上不是不能實現,但是要付出代價。通訊非常昂貴,時間同步困難且脆弱。

什麼是時間

時間是順序的來源。有了時間,我們才可以定義誰先誰後。分秒時只不過為了讓人理解恰好出現的記號。

假設時間以同樣的速率推進,(這是一個非常強的假設),時間和時間戳有下面解釋:

  • 順序(Order)例如,通過時間戳對事件進行排序
  • 持續時間(Duration)計算某事件持續時間,用於演算法判斷條件(例如監控心跳,判斷是否出現網路分割)
  • 解讀(Interpretaion)時間作為日期,本身包含一定意義,例如判斷下週日是否下雨。

分散式系統中每個節點都有獨立的本地時間和時間戳,於是事件的發生有本地的順序。但是該順序和其他節點完全獨立。給決定分散式系統中的全域性順序造成一定困難。

不可假設時間勻速流逝

分散式系統中應該儘量避免假設時間在不同節點上都以同樣的速率流逝,否則系統實現會比較脆弱。

那是否可以做到不同節點事件,一致而順序發生呢?有三個設計(假設)選擇。

  • 全域性時鐘(Global Clock):是
  • 本地時鐘(Local Clock):不全是
  • 沒有時鐘存在(No Clock):沒有

同步系統模型有全域性時鐘,部分同步模型有本地時鐘,而非同步系統模型沒有時鐘。

全域性時鐘假設

全域性時鐘是全序的源泉。

完美的時鐘,走時同步,存在於所有節點。這是分散式系統的理想假設。實際上,時鐘同步只能保證有限的精度。使用者可能隨機地改變本機時間,新節點加入,都有可能破壞全域性時鐘的假設。

當然,現實系統也有做出這個假設的。FB的Cassandra,就是使用時間戳來解決write的衝突的。時間戳較大的write會贏。那麼,如果時間不同步,那麼舊的write有可能覆蓋新的。

本地時鐘假設

假設,可能是目前比較合理的假設,本地有時鐘,但是不存在一個全域性時鐘。兩個節點的本地時間戳是不能比較的。

這和真實世界比較接近。事件在本地是可以排序的,但是在多節點分散式系統裡不行。不過可以在單機上計算timeout。

沒有時鐘假設

完全不使用”時鐘”這個概念,取而代之,“邏輯時間”。因為時間戳麼,只不過是當前世界狀態的一個快照,那我們用一個計數器(Counter),並和節點之間交流就可以做到了。

這樣,我們可以在不同的節點之間決定事件順序。不過有個壞處,因為缺乏時鐘,沒辦法決定timeout。

“沒有時鐘”的假設的一個實現是“Vector clocks”。後面會詳細講到。Cassandra的cousin Riak 和 Vodemort(LinkedIn)是它的應用。這些系統避免了全域性or本地時鐘漂移帶來的不確定性。

那麼事件的順序的準確性,完全是由通訊的延時來決定了。

向量時鐘 (Vector clocks)

Lamport時鐘向量時鐘通過計數器和通訊來決定分散式系統中事件發生順序的。計數器可以在不同節點之前進行比較。

Lamport時鐘

每個程序都維護一個計時器。

  • 當程序做了任意一件事,增加計時器計數。
  • 程序傳送的訊息中包含計時器計數。
  • 當收到訊息以後,計數器設定如下:max(local_counter, received_counter) + 1

Lamport時鐘定義了一個偏序,如果 timestamp(a) < timestamp(b):

  • a 可能發生在b之前
  • a和b壓根沒法比較

第二種情況發生在a和b所在的Partition沒有傳送通訊。

Vector clocks

向量時鐘是Lamport時鐘的一種擴充套件。它維護大小為N的數列[t1, t2, ....],N為節點數。每個節點都更新自己的時鐘。

  • 每當程序做了事情,更新該node的時鐘。
  • 程序傳送的訊息,包含上面提到的陣列。
  • 當收到訊息以後,更新本地的數組裡面的每個元素max(local, received);為當前節點的counter加1。

如下圖:

向量時鐘潛在的問題是每個節點都有個時鐘計數,對於大型的系統來說,向量本身可能變得很大。

失靈檢測

對於一個節點上的程式,它怎麼知道遠端某個節點失效了呢?在缺乏有效準確的全域性資訊下,我們可以通過一個合理的timeout值來確定。

但是合理的timeout值該怎麼確定呢?

失靈檢測器可以通過使用心跳訊息來實現timeout。節點之間交換心跳訊息。如果訊息在timeout之前沒有收到響應,就可以認為出現失效。

這種檢測要麼太沖動(把正常的節點算成失效),要麼太保守,很長時間才能檢測出錯誤。            

論文 http://www.google.com/search?q=Unreliable%20Failure%20Detectors%20for%20Reliable%20Distributed%20Systems 討論了失靈檢測在解決一致性問題中的兩大屬性: 完整性和精準性。

時間,順序和效能

我們知道在分散式系統中應假設偏序而不是全序。而要承諾全序也是可能的,但是代價非常大。通常的做法是告訴某一個master節點順序,讓它去執行。(GFS的control path做法)。這可能造成效能瓶頸。

時間,順序和同步真的必要麼?看情況。有時候可能你只不過需要最後的結果而不關係中間事件發生的順序。(Map Reduce)