前言
在一個分佈式系統中存在着各類各樣的併發事件,對於某些存在內在因果關係的事件須要知道事件的前後順序,而且可以按照正確的順序處理這些事件,區分事件的前後順序在單機系統中能夠靠本地時鐘來作到,但在分佈式系統中如何作到呢,這就是分佈式系統中的logical time問題。
本文爲你們介紹logical time算法的鼻祖Lamport Clock。算法
爲了形象地描述logical time問題,咱們舉個簡單的例子,假設客戶A下單購買了一本書,這時系統向訂單系統提交a請求(客戶買書的訂單),而後購買該書還有個優惠活動,可以得到一本贈書,這時系統須要向優惠活動管理系統發送b請求(客戶要求贈書x),優惠活動管理系統檢查准許客戶的贈書請求,因而將b請求轉發給訂單系統,在該例子中顯然訂單系統應該先收到買書的訂單,而後是贈書的訂單,可是因爲網絡延時的緣由,可能存在贈書請求先於買書請求到達訂單系統的狀況,那麼這種狀況須要如何處理?數據庫
咱們用簡單的圖來描述上面的過程,圖中P0表明訂單系統,P1表明客戶,P2表明優惠活動管理系統,a請求就是買書請求,b請求就是贈書請求。網絡
爲了解決該問題比較容易想到的作法就是同步通訊,發送a請求後等待P0處理完成並回復後再開始發送b請求,該方法簡單易實現可是並不能發揮分佈式系統的併發性能,效率低下,也不能簡單地用給時間a和b打上本地時間戳的方式來處理,由於分佈式系統中本地時鐘是沒法作到徹底同步的,因此須要一種適用於分佈式系統的能將事件的前後順序信息也被稱爲「 happened before」信息識別出來的算法,本文主要介紹logical time算法的鼻祖Lamport clock。併發
Lamport clock算法
Lamport clock算法的思想很簡單,主要有如下兩個規則:
1.每一個process在成功完成一個事件後都增長本身的時間戳,一般是加1;
2.(a)若是process Pi經過消息m發送了事件a,那麼該消息m中包含了當前pi的時間戳Ci(a);
(b)process Pj收到消息m後,取消息m中帶的時間戳和Pj當前的時間戳Cj中的較大值而後加1;
例如一個較爲複雜的例子,已經用Lamport clock算法爲每一個事件加了時間戳,以下圖:app
經過該例子能夠發現存在一些並無明確的前後關係的併發事件,好比p1上的時間戳爲3的事件和p2上的時間戳爲4的事件,這些事件能夠是任意前後或者同時發生,但在Lamport clock算法中這些事件卻有了明確的時間戳,該時間戳的大小並不表明事件的前後順序。分佈式
重要屬性
用簡單的公式來描述logical time算法的Clock Condition,C表示時間戳,ei 和 ej表示兩個事件,假設ei先於ej發生,並用->表示該「happened before」關係,那麼存在如下兩個Clock Condition:
1)ei -> ej => C(ei) < C(ej) 表示若是ei先於ej發生,那麼ei的時間戳C(ei)一定小於C(ej)。 2)ei -> ej <=> C(ei) < C(ej) 表示若是ei先於ej發生,那麼ei的時間戳C(ei)一定小於C(ej),若是C(ei)小於C(ej),那麼ei一定先於ej發生。性能
根據算法是否知足以上Clock Condition來區分其所具有的屬性,若是一個算法知足Clock Conditon 2,那麼該算法具有strongly consistent屬性,本篇文章介紹的Lamport clock算法只知足Clock Condition 1,因此不具有strongly consistent屬性,但後續介紹的vector clock算法具有strongly consitent屬性。 strongly consistent屬性的意義在因而否能夠經過C時間戳來判斷出事件ei與ej的順序關係,具有該屬性的算法,當時間戳C(ei) > C(ej)時,能夠肯定ei先於ej發生,不然能夠認爲ei與ej是衝突的(這裏的衝突表示ei與ej能夠是任意的前後關係),因此能夠用來檢測事件的衝突。blog
案例分析
使用Lamport clock對以前的例子作排序,以下圖:排序
P1發送a消息和b消息,由於P1的初始時間戳爲0,因此按照Lamport clock算法事件a和b的發送時間戳爲1和2。隊列
P0收到P1的消息a,取二者時間戳的較大值max(0,1)並+1獲得時間戳爲2。
P2收到b消息後,取二者時間戳的較大值max(0,2)並+1獲得時間戳爲3。
P0收到P2轉發的事件b後,取二者時間戳的較大值max(2,3)並+1獲得時間戳爲4。
因此在P0端能夠獲得事件a是先於事件b的。
但在實際的應用中因爲存在網絡延時,會出現如下狀況:
由於網絡延時致使P0先收到P2轉發的b事件,再收到P1的a事件,而後根據Lamport clock算法計算出來的時間戳也變成了b事件先於a事件了,這顯然是錯誤的,那麼要如何避免出現這個狀況,爲了關注解決該問題的實際算法,假定系統已經知足如下條件:
1.消息的接受順序與發送順序一致;
2.全部的消息最終都會被收到;
每一個process都有本身的請求隊列,而且對其餘process不可見,請求隊列中的初始時間戳爲0,算法由如下5條規則組成:
1.請求資源時,process Pi發送消息Tm,給其餘全部process,而且將消息Tm置於它的請求隊列中
2.prcocess Pj收到Pi的資源請求消息Tm後,將該消息置於本身的請求隊列中併發送一個帶有時間戳的回覆給Pi
3.釋放資源時,Pi將消息Tm從請求隊列中移除,併發送資源釋放消息給全部其餘process
4.process Pj收到Pi的資源釋放消息後將以前的資源請求消息Tm從請求隊列中移除
5.當知足如下2個條件時認爲Pi獲取了資源
(i) Pi的請求隊列中有請求消息Tm,而且按照順序排列好的,這裏以消息的發送順序爲準;
(ii) Pi收到了任意一個時間戳比Tm要大的消息;
把這個算法帶入到上面的例子中,至關於P1發起了兩個事件a和b來請求資源,a比b要先發生,那麼也指望a比b要先被P0處理(這裏處理能夠理解爲獲取了P0的資源),那麼當出現上述例子中的狀況,事件b先被P0收到,按照算法,P0發送Tm給全部其餘process,而後等待回覆,當收到P1的回覆時a事件也必然被收到了(按照系統假定知足的條件1)消息的接受順序與發送順序一致),這時按照規則5的(i)條件,會根據事件a和b的發送端的時間戳比較,從新排序爲a事件先於b事件,這樣就解決了由於網絡延時致使的消息亂序問題。
總結
Lamport clock雖然做爲分佈式系統中解決logical time問題的鼻祖,爲後續其餘算法提供了思路,但其不具有strongly consistent,沒法知足分佈式數據庫場景中寫衝突的檢測,因此實際場景中更可能是使用後來的vector clock,下一篇文章咱們將會給你們介紹vector clock。
參考 Lamport, L. (1978).「Time, Clocks, and the Ordering of Events in a Distributed System」