分佈式系統解決了傳統單體架構的單點問題和性能容量問題,另外一方面也帶來了不少的問題,其中一個問題就是多節點的時間同步問題:不一樣機器上的物理時鐘難以同步,致使沒法區分在分佈式系統中多個節點的事件時序。1978年Lamport在《Time, Clocks and the Ordering of Events in a Distributed System》中提出了邏輯時鐘的概念,來解決分佈式系統中區分事件發生的時序問題。html
邏輯時鐘是爲了區分現實中的物理時鐘提出來的概念,通常狀況下咱們提到的時間都是指物理時間,但實際上不少應用中,只要全部機器有相同的時間就夠了,這個時間不必定要跟實際時間相同。更進一步,若是兩個節點之間不進行交互,那麼它們的時間甚至都不須要同步。所以問題的關鍵點在於節點間的交互要在事件的發生順序上達成一致,而不是對於時間達成一致。算法
綜上,邏輯時鐘指的是分佈式系統中用於區分事件的發生順序的時間機制。 從某種意義上講,現實世界中的物理時間實際上是邏輯時鐘的特例。網絡
時間是在現實生活中是很重要的概念,有了時間咱們就能比較事情發生的前後順序。若是是單個計算機內執行的事務,因爲它們共享一個計時器,因此可以很容易經過時間戳來區分前後。同理在分佈式系統中也經過時間戳的方式來區分前後行不行?架構
答案是NO,由於在分佈式系統中的不一樣節點間保持它們的時鐘一致是一件不容易的事情。由於每一個節點的CPU都有本身的計時器,而不一樣計時器之間會產生時間偏移,最終致使不一樣節點上面的時間不一致。也就是說若是A節點的時鐘走的比B節點的要快1分鐘,那麼即便B先發出的消息(附帶B的時間戳),A的消息(附帶A的時間戳)在後一秒發出,A的消息也會被認爲先於B發生。併發
那麼是否能夠經過某種方式來同步不一樣節點的物理時鐘呢?答案是有的,NTP就是經常使用的時間同步算法,可是即便經過算法進行同步,總會有偏差,這種偏差在某些場景下(金融分佈式事務)是不能接受的。app
所以,Lamport提出邏輯時鐘就是爲了解決分佈式系統中的時序問題,即如何定義a在b以前發生。 值得注意的是,並非說分佈式系統只能用邏輯時鐘來解決這個問題,若是之後有某種技術可以讓不一樣節點的時鐘徹底保持一致,那麼使用物理時鐘來區分前後是一個更簡單有效的方式。分佈式
經過前面的討論咱們知道經過物理時鐘(即絕對參考系)來區分前後順序的前提是全部節點的時鐘徹底同步,但目前並不現實。所以,在沒有絕對參考系的狀況下,在一個分佈式系統中,你沒法判斷事件A是否發生在事件B以前,除非A和B存在某種依賴關係,即分佈式系統中的事件僅僅是部分有序的。性能
上面的結論跟狹義相對論有殊途同歸之妙,在狹義相對論中,不一樣觀察者在同一參考系中觀察到的事件前後順序是一致的,可是在不一樣的觀察者在不一樣的參考系中對兩個事件誰先發生可能具備不一樣的見解。當且僅當事件A是由事件B引發的時候,事件A和B之間才存在一個前後關係。兩個事件能夠創建因果關係的前提是:兩個事件之間能夠用等於或小於光速的速度傳遞信息。 值得注意的是這裏的因果關係指的是時序關係,即時間的先後,並非邏輯上的緣由和結果。cdn
那麼是否咱們能夠參考狹義相對論來定義分佈式系統中兩個事件的時序呢?在分佈式系統中,網絡是不可靠的,因此咱們去掉能夠和速度的約束,能夠獲得兩個事件能夠創建因果(時序)關係的前提是:兩個事件之間是否發生過信息傳遞。 在分佈式系統中,進程間通訊的手段(共享內存、消息發送等)都屬於信息傳遞,若是兩個進程間沒有任何交互,實際上他們之間內部事件的時序也可有可無。可是有交互的狀況下,特別是多個節點的要保持同一副本的狀況下,事件的時序很是重要。htm
分佈式系統中按是否存在節點交互可分爲三類事件,一類發生於節點內部,二是發送事件,三是接收事件。注意:如下文章中說起的時間戳如無特別說明,都指的是Lamport 邏輯時鐘的時間戳,不是物理時鐘的時間戳
邏輯時鐘定義
Clock Condition.對於任意事件
,
:若是
(
表示a先於b發生),那麼
, 反之否則, 由於有多是併發事件 C1.若是
和
都是進程
裏的事件,而且
在
以前,那麼
C2.若是
是進程
裏關於某消息的發送事件,
是另外一進程
裏關於該消息的接收事件,那麼
Lamport 邏輯時鐘原理以下:
假設有事件分別表示事件
對應的Lamport時間戳,若是
發生在
以前(happened before),記做
,則有
,例如圖1中有
,那麼
。經過該定義,事件集中Lamport時間戳不等的事件可進行比較,咱們得到事件的偏序關係(partial order)。注意:若是
,並不能說明
,也就是說
是
的必要不充分條件
若是,那
事件的順序又是怎樣的?值得注意的是當
的時候,它們確定不是因果關係,因此它們之間的前後其實並不會影響結果,咱們這裏只須要給出一種肯定的方式來定義它們之間的前後就能獲得全序關係。注意:Lamport邏輯時鐘只保證因果關係(偏序)的正確性,不保證絕對時序的正確性。
一種可行的方式是利用給進程編號,利用進程編號的大小來排序。假設分別在節點
上發生,
分別表示咱們給
的編號,若是
而且
,一樣定義爲
發生在
以前,記做
(全序關係)。假如咱們對圖1的
分別編號
,因
而且
,則
。
經過以上定義,咱們能夠對全部事件排序,得到事件的全序關係(total order)。上圖例子,咱們能夠進行排序:
觀察上面的全序關係你能夠發現,從時間軸來看是早於
發生的,可是在全序關係裏面咱們根據上面的定義給出的倒是
早於
,能夠發現Lamport邏輯時鐘是一個正確的算法,即有因果關係的事件時序不會錯,但並非一個公平的算法,即沒有因果關係的事件時序不必定符合實際狀況。
上面的分析過於理論,下面咱們來嘗試使用邏輯時鐘來解決分佈式鎖問題。
分佈式鎖問題本質上是對於共享資源的搶佔問題,咱們先對問題進行定義:
首先咱們假設,對於任意的兩個進程和
,它們之間傳遞的消息是按照發送順序被接收到的, 而且全部的消息最終都會被接收到。 每一個進程會維護一個它本身的對其餘全部進程都不可見的請求隊列。咱們假設該請求隊列初始時刻只有一個消息
資源請求,
表明初始時刻得到資源受權的那個進程,
小於任意時鐘初始值
下面我會用圖例來講明上面算法運做的過程,假設咱們有3個進程,根據算法說明,初始化狀態各個進程隊列裏面都是(0:0)狀態,此時鎖屬於P0。
接下來P1會發出請求資源的消息給全部其餘進程,而且放到本身的請求隊列裏面,根據邏輯時鐘算法,P1的時鐘走到1,而接受消息的P0和P2的時鐘爲消息時間戳+1。
收到P1的請求以後,P0和P2要發送確認消息給P1表示本身收到了。注意,因爲目前請求隊列裏面第一個不是P1發出的請求,因此此時鎖仍屬於P0。可是因爲收到了確認消息,此時P1已經知足了獲取資源的第一個條件:P1已經收到了其餘全部進程時間戳大於1的消息。
假設P0此時釋放了鎖(這裏爲了方便演示作了這個假設,實際上P0何時釋放資源均可以,算法都是正確的,讀者可自行推導),發送釋放資源的消息給P1和P2,P1和P2收到消息以後把請求(0:0)從隊列裏面刪除。
當P0釋放了資源以後,咱們發現P1知足了獲取資源的兩個條件:它的請求在隊列最前面;P1已經收到了其餘全部進程時間戳大於1的消息。 也就是說此時P1就獲取到了鎖。
值得注意的是,這個算法並非容錯的,有一個進程掛了整個系統就掛了,由於須要等待全部其餘進程的響應,同時對網絡的要求也很高。
若是你以前看過2PC,Paxos之類的算法,相信你看到最後必定會有一種似曾相識的感受。實際上,Lamport提出的邏輯時鐘能夠說是分佈式一致性算法的開山鼻祖,後續的全部分佈式算法都有它的影子。咱們不能想象現實世界中沒有時間,而邏輯時鐘定義了分佈式系統裏面的時間概念,解決了分佈式系統中區分事件發生的時序問題。