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.記作 ->

a->b意味着所有的進程都agree事件a發生在事件b之前。

在三種情況下,可以很容易的得到這個關系:

  1. 如果事件a和事件b是同一個進程中的並且事件a發生在事件b前面,那么a->b

  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的先后。

Vector Clock

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

  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM