在上一篇文章分散式系統:Lamport 邏輯時鐘中我們知道Lamport 邏輯時鐘幫助我們得到了分散式系統中的事件全序關係,但是對於同時發生的關係卻不能很好的描述,導致無法描述事件的因果關係。向量時鐘是在 Lamport 時間戳基礎上演進的另一種邏輯時鐘方法,它通過向量結構不但記錄本節點的 Lamport 時間戳,同時也記錄了其他節點的 Lamport 時間戳,因此能夠很好描述同時發生關係以及事件的因果關係。
注意:
- 本文中的因果關係指的是時序關係,即時間的前後,並不是邏輯上的原因和結果
- 本文中提及的時間戳如無特別說明,都指的是邏輯時鐘的時間戳,不是物理時鐘的時間戳
為什麼需要向量時鐘
首先我們來回顧一下 Lamport 邏輯時鐘演算法,它提供了一種判斷分散式系統中事件全序關係的方法:如果 a -> b,那麼 C(a) < C(b),但是 C(a) < C(b) 並不能說明 a -> b。也就是說C(a) < C(b) 是 a -> b 的必要不充分條件,我們不能通過 Lamport 時間戳對事件 a、b 的因果關係進行判斷。 下面我們舉一個例子來說明。
假設有三個程式在發訊息,Ts(mi)表示訊息mi的傳送時間戳,Tr(mi)表示訊息mi的接受時間戳,顯然 Ts(mi) < Tr(mi),但是這個能說明什麼呢?我們可以發現在程式 P2 中,Tr(m1) < Ts(m3),說明 m3 是在 m1 被接收之後傳送的,也就是說 m3 的傳送跟 m1 的接收有關係。難道通過 Lamport 時間戳就能區分事件的因果的關係了嗎?答案是 No,我們仔細看可以發現,雖然 Tr(m1) < Ts(m2),但實際上 m2 的傳送跟 m1 並沒有關係。
綜上所述,我們可以發現 Lamport 邏輯時鐘演算法中每個程式只擁有自己的本地時間,沒有其他程式的時間,導致無法描述事件的因果關係。如果每個程式都能夠知道其他所有程式的時間,是否就能夠得到事件的因果關係了呢?為此,有人提出了向量時鐘演算法,在 Lamport 邏輯時鐘的基礎上進行了改良,提出了一種在分散式系統中描述事件因果關係的演算法。
可能有人會有疑問:向量時鐘到底有什麼用呢?舉一個常見的工程應用:資料衝突檢測。分散式系統中資料一般存在多個副本,多個副本可能被同時更新,這會引起副本間資料不一致,此時衝突檢測就非常重要。**基於向量時鐘我們可以獲得任意兩個事件的順序關係,結果要麼是有因果關係(先後順序),要麼是沒有因果關係(同時發生)。**通過向量時鐘,我們能夠識別到如果兩個資料更新操作是同時發生的關係,那麼說明出現了資料衝突。後面我們會詳細說明相關的實現。
什麼是向量時鐘
通過上面的分析我們知道向量時鐘演算法是**在 Lamport 邏輯時鐘的基礎上進行了改良,用於在分散式系統中描述事件因果關係的演算法。**那麼為什麼叫向量時鐘呢?前面我們知道如果每個程式都能夠知道其他所有程式的時間,就能夠通過計算得到事件的因果關係。向量時鐘演算法利用了向量這種資料結構將全域性各個程式的邏輯時間戳廣播給各個程式:每個程式傳送事件時都會將當前程式已知的所有程式時間寫入到一個向量中,附帶在訊息中。這就是向量時鐘命名的由來。
如何實現向量時鐘
假設分散式系統中有 N 個程式,每個程式都有一個本地的向量時間戳 Ti,向量時鐘演算法實現如下:
- 對於程式 i 來說,Ti[i] 是程式 i 本地的邏輯時間
- 當程式 i 當有新的事件發生時,Ti[i] = Ti[i] + 1
- 當程式 i 傳送訊息時將它的向量時間戳(MT=Ti)附帶在訊息中。
- 接受訊息的程式 j 更新本地的向量時間戳:Tj[k] = max(Tj[k], MT[k]) for k = 1 to N。(MT即訊息中附帶的向量時間戳)
下圖是向量時鐘的示例:
那麼如何利用向量時鐘判斷事件的因果關係呢?我們知道分散式系統中的事件要麼是有因果關係(先後順序),要麼是沒有因果關係(同時發生),下面我們來看一下如何利用向量時鐘判斷時間的因果關係。
假設有事件 a、b 分別在節點 P、Q 上發生,向量時鐘分別為 Ta、Tb,如果 Tb[Q] > Ta[Q] 並且 Tb[P] >= Ta[P],則a發生於b之前,記作 a -> b,此時說明事件 a、b 有因果關係; 反之,如果 Tb[Q] > Ta[Q] 並且 Tb[P] < Ta[P],則認為a、b同時發生,記作 a <-> b。例如上圖中節點 B 上的第 4 個事件 (A:2,B:4,C:1) 與節點 C 上的第 2 個事件 (B:3,C:2) 沒有因果關係,屬於同時發生事件。
向量時鐘的實際應用
前面我們提到向量時鐘可以用來檢測分散式系統中多副本更新的資料衝突問題,注意是檢測(發現問題),它並不能解決問題。資料衝突的解決是另一個課題,這裡不展開了。
亞馬遜的 Dynamo 是一個分散式Key/Value儲存系統,為了高可用,即使在出現網路分割槽或者機器當機時依然可讀可寫。當網路分割槽恢復之後,多個副本同步資料一定會出現資料不一致的情況,那麼如何檢測資料衝突呢?參考向量時鐘(Vector clock)的思想,Dynamo 中使用了版本向量(Version vector)來檢測資料衝突,下面我們來看看演算法的實現。
- client 端寫入資料,該請求被 Sx 處理並建立相應的 vector ([Sx, 1]),記為資料 D1
- 第 2 次請求也被 Sx 處理,資料修改為 D2,vector 修改為([Sx, 2])
- 第 3、4 次請求分別被 Sy、Sz 處理,client 端先讀取到 D2,然後 D3、D4 被寫入 Sy、Sz
- 第 5 次更新時 client 端讀取到 D2、D3 和 D4 3個資料版本,通過類似向量時鐘判斷同時發生關係的方法可判斷 D3、D4 是同時發生的事件,因此存在資料衝突,最終通過一定方法解決資料衝突並寫入 D5
注意,向量時鐘和版本向量並不是同一個東西,版本向量借鑑了向量時鐘中利用向量來判斷事件的因果關係的思想,用於檢測資料衝突。向量時鐘還有其他的應用,例如強制因果通訊(Enforcing Causal Communication),這裡不展開了,有興趣的讀者自行谷歌。
總結
向量時鐘演算法利用了向量這種資料結構將全域性各個程式的邏輯時間戳廣播給各個程式,通過向量時間戳就能夠比較任意兩個事件的因果關係(先後關係或者同時發生關係)。向量時鐘被用於解決資料衝突檢測、強制因果通訊等需要判斷事件因果關係的問題。