分佈式鎖(邏輯時鐘)原理 Leslie Lamport論文筆記

分佈式系統中的時間、時鐘和事件順序 論文筆記

這篇筆記主要是用於記錄閱讀《Time, Clocks, and the Ordering of Events in a Distributed System》論文的要點以及我自己對於分佈式系統時鐘的理解。之前已經閱讀過這篇文章幾次了,每次閱讀都對自己很有幫助。因此寫下這篇論文筆記可以幫助自己進一步加深理解。

1978年Lamport在這篇論文中主要討論的是分佈式系統下的時鐘和事件序問題,摒棄了物理時鐘,提出了邏輯時鐘的概念來解決分佈式系統中區分事件發生的時序問題。

分佈式系統概念

分佈式系統由不同的進程組成,不同進程可以經由消息通信。這篇論文說的分佈式系統是從廣義上來說的,一個互聯網中的多計算機節點是分佈式系統;單臺機器的CPU、內存等內部組件也共同構成了一個分佈式系統。事實上,如果相比組件內事件發生間隔而言,組件間的消息通信延遲不能忽略的話,這個系統就可以被認爲是分佈式系統。

物理時鐘vs邏輯時鐘

爲什麼分佈式系統不適用物理時鐘(physical clock)記錄事件?每個事件對應打上一個物理時鐘時間戳,當需要比較順序的時候比較相應時間戳即可。這是因爲在現實世界中物理時鐘有世界統一的標準,但是在分佈式系統中每個計算機節點記錄的時間是不一樣的,即使通過設置NTP時間同步,各個節點間也存在毫秒級的偏差。每個節點的CPU都有自己的計時器,而不同計時器之間會產生時間偏移,最終導致不同節點上面的時間不一致。假設如果A節點的物理時鐘比B節點的要快1分鐘,那麼即使B先發出的消息,A的消息在後面1s發出,那麼在整個分佈式系統中A的消息也會被認爲比B節點消息先發出。但是在分佈式系統中,NTP時間同步算法就是常用來同步不同節點的物理時鐘。但是這種方式也會產生誤差,這種級別的誤差在金融分佈式事務的場景下是不能接受的。

因此當我們在考慮對一個時鐘系統的正確性時,我們不能將定義的正確性基於物理時鐘之上,因爲這需要引入持有物理時鐘標準。因而分佈式系統需要有另外的方法記錄事件順序關係,這就是邏輯時鐘(logical clock)。

事件序列

論文的核心在於討論事件序列。事件序列指的是特定環境中的事件發生的先後順序。時間只不過是事件序列的一種定義方法。即使是通過時間來定義,也存在物理時間、邏輯時間等方法。

一個分佈式系統由衆多獨立的節點組成,稱爲Process。每個節點的事件來源有兩類:

  1. 內部事件:此類事件由節點內部產生,可以認爲與其他節點不產生關聯;
  2. 外部事件:此類事件由其他節點個體刺激本節點個體產生,這種外部事件的表現形式通常爲消息。

事件序列分爲兩種:偏序事件序列和全序事件序列。

在數學上,順序又是如何描述的呢?

我們先看一下序理論中的兩種序關係:偏序(partial ordering)和全序(total ordering)。

偏序

偏序的數學定義:

假設≤是集合S上的一個二元關係,如果≤滿足:

  1. 自反性:對於S中任意的元素a,都有a≤a;
  2. 反對稱性:如果對於S中的兩個元素a和b,a≤b且b≤a,那麼a=b;
  3. 傳遞性:如果對於S中的三個元素,有a≤b且b≤c,那麼a≤c。

上面的內容可以先不用理解,關鍵點在於:偏序關係是一種序關係,但只是部分元素有序,並不是全部都可以比較。這裏的部分元素其實是有因果關係的事件。

例如,發生在個體PA的一個事件E1,該事件的影響是產生了消息M,M傳遞至個體B,進而導致事件E2的發生,即說明E1->E2:E1導致了E2的產生。而對於兩個相互獨立的事件,我們無法從原理上判斷事件先後順序。

在分佈式系統中,一個進程process內的多個事件,自然地具備事件的先後順序,或者說一個process本身就是一個有先驗順序的全序的事件集合。除了process內在的順序,消息的發送和接收事件也是有因果序的。基於這兩點,我們定義happen-before關係,寫作->。

happen-before關係->,是一個分佈式系統中事件集合上滿足如下條件的最小關係:

  • 在同一個process內部,如果事件a早於事件b發生,稱爲a->b;
  • 如果a和b是兩個不同process的事件,且b是a發送的消息的接收者,同樣a->b;
  • 滿足傳遞性,如果a->b,b->c,那麼a->c。

如果a↛b∧b↛a,那麼稱事件ab是併發的。

Lamport在論文中提出了一種利用邏輯時鐘設計的一種偏序系統方法:

  • 每個process存在獨立的事件序列發生器,每次產生新的事件,該序列發生器自增1,並將結果賦予該事件;
  • 如果process的事件E需要向其他process發送消息M,那麼在M中攜帶E的序列號;
  • 如果Process收到外部消息M,獲取M中攜帶的序列號,與自身的事件序列發生器比較並取最大值,然後自增1,賦給由於M而觸發的新的外部事件。

根據上面的定義,我們可以得到如下結論:

  • process內部的事件均可以比較先後順序;
  • process之間的因果事件可以確定先後順序,而process之間的獨立事件則無法比較。

在圖中,a->b意味着從a可以沿着線到b,例如p1->r4。因爲有:p1->q2,q2->q3,q3->q4,q4->r3,r3->r4。而p3->q3不成立。這個定義的另外一種理解:a->b意味着從a和b有因果序,例如p3和q3是併發的,意味着我們不能推斷兩者的時序。

全序

全序比偏序的要求更爲嚴格一些,在偏序的基礎上,多了一個完全性的條件:

  • 完全性:對於S中的任意a、b元素,必然有a≤b或b≤a。

實際上,全序是在偏序的基礎上,要求集合內全部元素都必須可以比較。

總體來說,偏序是部分可比較的序關係,全序是全部可比較的序關係。

全序與偏序的比較

我們可以通過簡單的有向圖來描述全序和偏序的不同:

上圖中S1上的圖示關係,描述的是整數之間的大小順序,是一種全序關係,可以看到任意兩個元素之間可以比較順序。而S2上的關係,描述的是集合之間的包含關係,是一種偏序關係,其中v2和v3是不可比較的。

邏輯時鐘Logical Clocks

邏輯時鐘定義

Leslie Lamport在1978年提出邏輯時鐘的概念,並描述了一種邏輯時鐘的表示方法,這個方法被稱爲Lamport時間戳(Lamport timestamps)。概念上,時鐘即給事件分配序號的方式。

分佈式系統按是否存在節點交互可分爲三類事件,一類發生於節點內部(下圖中的紅色標記的事件),二是發送事件(下圖中的藍色標記事件),三是接收事件(下圖中的黑灰色標記事件)。如下圖所示:

定義Ci爲進程Pi的時鐘,Ci爲進程Pi的事件a的序號。不關心哪個進程時,Ci記爲C。該時鐘與物理時鐘physical time沒有關係。

我們應該基於事件的順序來定義正確性,禁止引入物理時鐘來定義邏輯時鐘的正確性。如果兩個事件有happen-before關係,時鐘序號應該與關係一致,即Clock Condition。

Clock Condition:任意事件a,b,如果a->b,那麼C≤C

該命題的逆命題不成立。

根據之前happen-before關係的定義,顯然只要如下的兩個條件C1、C2成立,Clock Condition則成立:

C1.如果a,b是Pi的事件,且a在b之前發生,則定義Ci≤Ci

C2.如果a是Pi發送消息事件,b是Pj接收消息事件,定義Ci≤Cj

如何保證C1和C2這兩個條件成立呢?只需要根據實現規則implement rule IR1和IR2即可保證C1和C2.

IR1.Pi的兩個連續事件之間要遞增該process的Ci。

IR2.a是Pi發送消息事件,那麼消息m中攜帶一個時間戳Tm=Ci。Pj接收到消息m之後,Pj需要將Cj設置爲等於或大於當前值,且大於Tm的值。

從時空圖的角度考慮時鐘,我們假設進程的時鐘在每個事件之間tick,且每次tick增加1。比如a、b是Pi進程中的事件,如果Ci=4,C1=7,那麼這兩個事件之間時鐘會走過5、6、7。如果我們通過時間線將所有tick的點連接起來,那麼Fig1中的時空圖會變成這樣:

tick line實際上就是時空笛卡爾座標系的時間軸,將時間線拉直就能得到下圖Fig3。

邏輯時鐘原理

  1. 每個事件對應一個Lamport時間戳,初始值爲0.
  2. 如果事件在節點內發生,時間戳加1.
  3. 如果事件屬於發送事件,時間戳加1並在消息中帶上該時間戳.
  4. 如果事件屬於接收事件,時間戳=Max(進程本地時間戳,消息中的時間戳)+1.

假設有事件a、b,C(a)、C(b)分別表示事件a、b對應的Lamport時間戳,如果a->b,則C(a)<C(b),a發生在b之前,例如上圖中所示,C1->B1,那麼C(C1)<C(B1)。通過該定義,事件集中Lamport時間戳不等的事件可以進行比較,我們獲得事件的偏序關係。

如果C(a)=C(b),那麼a、b事件的順序是如何判定的?首先能夠確定的是,當C(a)=C(b)時,這兩者之間肯定不是因果關係,所以它們之間的先後順序並不會影響結果,我們這裏只需要給出一種確定的方式來定義它們之間的先後順序就能夠擴展得到全序關係。假設a、b分別在節點P、Q上發生,Pi、Qj分別表示給我們P、Q的進程編號,如果C(a)=C(b)並且Pi<Qj,同樣定義爲a發生在b之前,記作a=>b(全序關係)。假如我們對圖1的A、B、C分別編號Ai=1、Bj=2、Ck=3,因C(B4)=C(C3)並且Bj<Ck,則B4=>C3。

通過以上定義,我們可以對所有事件排序,獲得事件的全序關係。按照上圖中的因果順序以及判定方法,事件排序爲:C1=>B1=>B2=>A1=>B3=>A2=>C2=>B4=>C3=>A3=>B5=>C4=>C5=>A4。其中B5在時間軸上顯示比A3先發生,但是我們根據判定方法,全序關係爲A3=>B5。

用全序關係解決分佈式互斥問題(分佈式鎖資源搶佔)

問題描述:單機多進程程序可由鎖進行同步,是因爲這些進程都運行在操作系統上,有center爲每個進程的資源請求排序,這個center知道所有需要進行同步的進程的所有信息。但是在分佈式系統中,各個進程運行在各自的主機上,沒有center的概念,那麼分佈式系統中多進程應該如何進行同步?也就是分佈式鎖應該如何實現?多個進程共享一個資源。要求同一時間只有一個進程能夠使用該資源,其實這就是分佈式鎖的問題。Lamport的論文中提出瞭解決這個問題的算法需要滿足下面三個條件:

  1. A process which has been granted the resource must release it before it can be granted to another process;(已經獲得資源的進程先釋放,其他進程才能夠獲得資源授權)
  2. Different requests for the resource must be granted in the order in which they are made;(資源授予順序需要按照申請的順序)
  3. If every process which is granted the resource eventually releases it,then every request is eventually granted。(如果已經獲得資源的進程釋放了資源,其他想申請的進程最終能夠獲得資源)

爲了簡化問題,我們做如下假設:

  • 任何兩個進程Pi,Pj,它們之間接收到的消息的順序與發送消息的順序一致,並且每個消息一定能夠被接收到。
  • 每個進程都維護一個不被其他進程所知的請求隊列。並且請求隊列初始化爲包含一個T0:P0資源請求,P0用於該共享資源,T0是初始值小於任何時鐘值

算法如下:

  1. To request the resource,process Pi sends the message Tm:Pi requests resource to every pther process,and puts that message on its request queue,where Tm is the timestamp of the message。爲請求該項資源,進程Pi發送一個(Tm:Pi)資源請求(請求鎖)消息給其他所有進程,並將該消息放入自己的請求隊列,在這裏Tm代表了消息的時間戳。
  2. When process Pj receives the message Tm:Pi requests resource,it places it on its request queue and sends a (timestamped) acknowledgment message to Pi。當進程Pj收到(Tm:Pi)資源請求消息後,將它放到自己的請求隊列中,併發送一個帶時間戳的確認消息給Pi。
  3. To release the resource,process Pi removes any Tm:Pi requests resource message from its request queue and sends a (timestamped) Pi releases resource message to every other process。釋放該項資源(釋放鎖)時,進程Pi從自己的消息隊列中刪除所有的(Tm:Pi)資源請求,同時給其他所有進程發送一個帶有時間戳的Pi資源釋放消息。
  4. When process Pj received a Pi releases resource message,it removes any Tm:Pi requests resource message from its request queue。當進程Pj收到Pi資源釋放消息後,它就從自己的消息隊列中刪除所有的(Tm:Pi)資源請求。
  5. Process Pi granted the resource when the following two conditions are satisfied:當同時滿足如下兩個條件時,就將資源分配(鎖佔用)給進程Pi:
  • There is a Tm:Pi requests resource message in its request queue which is ordered before any other request in its queue by the relation 。按照全序關係排序後,(Tm:Pi)資源請求排在它的請求隊列的最前面
  • Pi has received a message from every other process timestamped later than Tm。Pi已經從所有其他進程都收到了時間戳>Tm的消息

下面我們通過一個示例說明上面的獲取資源算法過程,假設我們有3個進程,根據算法說明,初始化狀態各個進程請求隊列裏面都是(0:0)狀態,此時鎖屬於P0.

接下來P1會發出請求資源的消息給所有其他進程P0,P2,並且放到自己的請求隊列裏面,根據邏輯時鐘算法,P1的時間戳加1,而接收消息的P0和P2的邏輯時鐘時間戳爲消息時間戳+1=2.

收到P1的請求消息之後,P0和P2要發送確認消息給P1表示自己收到消息了。由於目前請求隊列裏面第一個不是P1發出的請求,所以此時鎖仍然屬於P0。但是由於收到了確認消息,此時P1已經滿足了獲取資源的其中一個條件:P1已經收到了來自其他所有進程時間戳大於1的消息。

假設P0此時釋放了鎖,發送釋放資源的消息給P1和P2,P1和P2收到消息之後把請求(0:0)從隊列裏面刪除。

當P0釋放了資源之後,我們發現P1滿足了獲取資源的兩個條件:它的資源請求(1:1)在隊列最前面;P1已經收到了其他所有進程時間戳大於1的迴應消息。此時P1獲取到了鎖。

其實關鍵思想不復雜,既然分佈式系統中沒有center的概念,那我請求共享資源時我就讓其他所有進程都知道我要請求該資源,擁有資源的進程釋放資源時也告訴所有進程,我要釋放該資源,想請求該資源的你們可以按序(邏輯時鐘的作用,這裏重點說明邏輯時鐘不能保證在絕對物理時間上請求的排序)請求了。這樣每個進程都知道其他進程的狀態,和center的作用是一樣的。

對於分佈式鎖問題,多個請求不一定是按照絕對物理時鐘排序纔可以,只要我們有這樣一個算法,這個算法可以保證多個進程的請求按照這個算法總能得到同一個排序,就可以了。而按照絕對物理時鐘排序只是其中一個可行的算法。

但是我們需要特別注意,這種算法並不是容錯的,它要求所有進程都非常可靠,一旦一個進程掛了或者出現網絡分區的情況,是無法工作的,同時對我們提出的網絡要求也非常嚴格,要求發出的消息一定被接收到,這個在生產系統中是很難做到的。所以這是一個理想狀況下的算法實現,達不到一個工業級的算法要求。但是對於分佈式系統來說,仍然是非常有意義的,Lamport提出的邏輯時鐘概念可以說是分佈式一致性算法的開山鼻祖,後續的所有分佈式算法都有它的影子。邏輯時鐘定義了分佈式系統裏面的時間概念,解決了分佈式系統中區分事件發生的時序問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章