Vector Clock/Version Clock

physical clock

機器上的物理時鐘,不一樣的機器在同一個時間點取到的physical clock不同,之間會存在必定的偏差,NTP能夠用來控制這個偏差,機器之間的時鐘偏差能夠控制在幾十ms之內。兩個事件a和b,a在機器M1上physical clock爲12點5分0秒6ms發生,b在機器M2上physical clock爲12點5分0秒7ms發生,這並不表明a發生在b以前,由於兩個機器上取到的physical clock和真實時間(這個時間就是國際標準時間UTC,能夠經過原子鐘,internet,衛星得到)之間都有偏差。好比機器M1的physical clock比真實時間慢10ms,那麼事件a其實是在真實時間12點5分0秒16ms發生的,機器M2的physical clock比真實事件慢5ms,那麼事件b的其實是在真實時間12點5分0秒12ms發生的,顯然,事件a發生在事件b以後。算法

Lamport's Logical Clock

單機系統容易給發生的全部事件定義一個全局順序(total order),可是分佈式系統沒有全局時鐘,很難給全部事件定義一個全局順序。因此,Lamport定義了一種偏序關係,happens-before.記做 ->bash

a->b意味着全部的進程都agree事件a發生在事件b以前。服務器

在三種狀況下,能夠很容易的獲得這個關係:網絡

  1. 若是事件a和事件b是同一個進程中的而且事件a發生在事件b前面,那麼a->bapp

  2. 若是進程A發送一條消息m給進程B,a表明進程A發送消息m的事件,b表明進程B接收消息m的事件,那麼a->b(因爲消息的傳遞須要時間)
  3. ->知足傳遞性,若是a->b AND b->c => a->c異步

Lamport's Logical Clock算法以下:分佈式

  1. 每一個機器本地維護一個logical clock LCi
  2. 每一個機器本地每發生一個事件設置LCi = LCi + 1,而且把結果做爲這個事件的logical clock。
  3. 當機器i給機器j發送消息m時,把LCi存在消息裏。
  4. 當機器j收到消息m時候,LCj = max(LCj, m timestamp)+1,結果值做爲收到消息m這個事件的時間戳。

這個算法可以保證a->b,那麼a事件的logical clock比b事件的logical clock小。反過來,經過只比較兩個事件的logical clock不能獲得a和b的前後。wordpress

Vector Clock

每一個機器維護一個向量VC,也就是Vector Clock,這個向量VC有以下屬性:post

  1. VCi[i] 是到目前爲止機器i上發生的事件的個數設計

  2. VCi[k] 是機器i知道的機器k發生的事件的個數(即機器i對機器j的知識)

每一個機器都有一個向量(Vector),每一個向量中的元素都是一個logical clock,因此取名爲Vector Clock。

經過以下算法更新Vector Clock

  1. 機器i本地發生一個事件時將VCi[i]加1
  2. 機器i給機器j發送消息m時,將整個VCi存在消息內
  3. 機器j收到消息m時,VCj[k]=max(VCj[k],VCi[k]),同時,VCj[j]+1

能夠看出,Vector Clock是一種maintain因果關係(causality)的一種手段,Vector Clock在機器之間傳遞達到給對方傳遞本身已有的關於其餘機器知識的目的。

Dynamo爲何須要Vector Clock(其實是Version Clock)

Dynamo是一個分佈式Key/Value存儲系統,這個Value能夠是一行,包含多個列, 爲了容錯,每一個Key/Value保存多副本,一般在不一樣的機器上,通常是3,後面以3爲例。對外是一個最終一致性系統,即客戶端A寫入一個值返回成功後,在必定的時間內另一個客戶端可能讀不到最新的值。一般,成功寫入兩個副本成功即返回給客戶端成功,同時請求會異步的同步到第三個副本。然而,高可用是Dynamo的主要設計目標之一,即便在出現網絡分區或者機器宕機時依然可讀可寫。

假設Key K有三個副本k1,k2,k3分別在M1,M2,M3上。

正常狀況

M1處理寫請求,M1將請求發往M2,M3,只要有一個返回,即返回客戶端成功。

網絡分區

若是M1和M2/M3之間網絡都不通,k1被更新(持續高可用,依然給客戶端返回成功),隨後,其餘節點(集羣中任意一個節點均可以接客戶端請求,而且將請求路由到正確的節點上)路由了寫請求給M2(假設其餘節點和M1/M3之間網絡不通),k2被更新。這時,k1和k2數據不同,最後網絡恢復,三個副本進行同步時,應該保留哪一個版本?若是隻保留k2,即採用last write win機制,那麼同步後,第一個客戶端會發現它寫的數據丟了。

這個時候就須要Vector Clock,更確切的說是Version Clock。

爲了處理這種場景,Dynamo使用Version Clock來捕獲同一個Object的不一樣版本之間的causality。每一個Object的每一個版本會有一個相關聯的Version Clock, 形如[(serverA,counter),(serverB,counter),...], 經過檢查同一個Object不一樣版本的Version Clock,能夠決定是否能夠徹底丟棄一個版本,僅保留另一個版本,仍是須要將兩個版本進行merge。若是Object的版本A的VCA包含的每項(server, counter)在版本B的VCB中都有對應項,而且counter小於等於版本B中對應項的counter(記做VCB descends VCA),那麼這個Object的版本A能夠被丟棄,不然須要對兩個版本進行merge。

回到剛纔的例子,k1被更新,Version Clock(注:此處假設k1/k2/k3三個副本以前如出一轍,那麼就能夠省略以前的Version Clock)爲[(M1,1)],k2被更新,Version Clock爲[(M2,1)],隨後k1/k2網絡通了,他們經過比較兩個Version Clock發現兩個Version Clock存在衝突,不是descends的關係,那麼就兩個版本都保留,當客戶端來讀Key K的時候,兩個版本的數據和對應的VC都返回給客戶端,由客戶端進行衝突合併,客戶端進行衝突合併後寫入Key K的時候,帶着合併後的VC[(M1, 1), [M2, 1]]發到M1/M2,覆蓋服務器版本,衝突解決。

能夠看出,Vector Clock最初是爲了給分佈式系統的事件定序發明的,本質上是一種捕獲causality的手段,只是他們捕獲的是事件的關係。而Version Clock是捕獲同一個數據的不一樣版本之間的causality.

Riak這個系統也使用了Vector Clock來作衝突合併,對Vector Clock的用法可謂比較深刻,具體能夠看最後兩篇參考資料。

參考資料

Dynamo

Scalable and Accurate Causality Tracking
for Eventually Consistent Stores

version-vectors-are-not-vector-clocks

Causality Is Expensive

Vector Clocks Revisited

vector-clocks-revisited-part-2-dotted-version-vectors

相關文章
相關標籤/搜索