如果把一個分散式系統類比成現代社會的協作網路,那每一個分散式系統中的節點就是參與我們社會協作的每一個人,節點之間的通訊就是人與人之間的溝通交流,節點完成自己的計算任務也可以類比成我們每一個人完成自己的本職工作。從這個意義上來說,現代社會協作網路的構建目標可以說和分散式系統有很多的一致的地方,比如我們希望社會運轉更加高效(高效),我們希望社會是安全的(安全),我們希望社會更加包容和多元(異質性),我們希望社會的運轉不會因為一部分人或者機構因故不能正常參與協作而產生問題(魯棒性)等等。雖然不是非常的嚴謹,畢竟人類不是隻會工作的勞動機器,但是我們或許可以藉助這個類比思考一些分散式系統中的基本問題。
現代社會的協作網路之所以能夠運轉起來的最基礎的保證是什麼呢?我想一定是時鐘的概念。人們上下班、與人約會等日常行為基本都起碼要遵循一定範圍內的同一個時鐘,也是為什麼一旦不同時區的人需要協作,就需要考慮時差的原因。同樣地,在分散式系統中,每個獨立的節點彼此之間需要合作,就需要遵循同一個時鐘。在介紹分散式系統中的時鐘之前,我們需要先介紹兩種型別的分散式系統,因為時鐘的概念在這兩者之間有些微的差異。
同步系統和非同步系統
分散式系統,根據節點之間通訊的特點,可以分為同步系統(Synchronous Systems)和非同步系統(Asynchronous Systems)。其中,同步系統對節點之間的通訊的要求較為嚴格,要求節點之間的訊息延遲必須有一個上限,這個上限可以很大,但是必須要有。相反,非同步系統則更加接近真實世界,它不要求節點之間的訊息延遲有一個確定的上限,甚至允許節點之間傳遞的訊息可以丟失。
為什麼要用節點之間的訊息傳遞的特點來劃分系統的型別呢?答案其實很簡單,任何一個分散式系統都需要依靠節點之間的協作才能完成其使命,而節點之間協作的最根本的基礎就是通訊。可以說,沒有節點之間的通訊,分散式系統就不存在了。節點之間的通訊是分散式系統中最重要的研究問題。
說回到同步和非同步,透過這兩類系統的定義不難發現,同步系統提供了很好的通訊環境,這可以使得後續的很多問題的解決變得很簡單。儘管同步系統的假設非常的美好,但是現實世界中構建的分散式系統很難保證節點之間訊息傳遞延遲的上限,或者說構建一個這樣的系統會有極高的成本,也就限制了同步系統的大規模建設。因此,世界上絕大多數研究分散式系統的工作都建立在非同步系統的假設上。讀者也不難發現,如果以同步系統作為假設,我們後續討論時鐘、快照和共識都可以用一個極為簡單的演算法完成,也只有在非同步系統的假設下,上述三個問題才有了被討論的價值。
分散式時鐘——Lamport時鐘和向量時鐘
前文提到時鐘是多個獨立的節點之間能夠相互協作的基礎。那豈不是隻需要為所有節點設定一個時鐘就好了,這為什麼會成為一個值得深入討論的問題呢?的確,人與人之間的協作確實只需要遵循同一個時鐘,比如統一遵循北京時間或者格林威治時間。但是這麼做的前提是時鐘的誤差要遠遠小於人們使用時間的尺度。無論是上下班、開會還是趕高鐵飛機,人們使用時鐘的尺度大都在分鐘這個級別,甚至用到秒這個級別的情況都很少。即便不考慮世界上誤差最小的鋁離子光鍾(三百億年累計的誤差小於一秒),即便是目前智慧手機裡內建的時鐘,也能夠提供毫秒級甚至更加精細的計時,因此完全不需要考慮誤差造成的影響。
然而,在分散式系統中則不同,在相互獨立的節點之間維護一個統一的時鐘是一個近乎不可能完成任務,沒有人能保證一個分散式系統中的上百萬個節點裡的時鐘都是從同一時刻開始,並且以相同速度計時。總的來說,物理時鐘可能發生的兩類誤差是無法在分散式系統中被自動修復的,一個是時鐘傾斜(clock skew),一個是時鐘漂移(clock drift)。通俗來講,如果節點A的時鐘顯示當前時間是當天的下午兩點,而節點B顯示的是當天下午三點,這就是時鐘傾斜;而如果這個時候節點A的時鐘計時的速度比B的快,比如節點A的時鐘從兩點到三點實際用了59分鐘,而B則實際用了61分鐘,這種現象叫做時鐘漂移。無論是時鐘傾斜還是時鐘漂移,這兩種誤差都無法被分散式系統自動修復,一方面是因為不同節點的傾斜情況和漂移情況可能千奇百怪,另一方面則是如果透過通訊的方式校正,那麼訊息傳遞的時間是無法被準確估算的,即當一個節點向另一個節點傳送一條訊息“我的當前時間為下午3點整”,那麼另一個節點接收到這個訊息的時候,傳送訊息的節點的當前時間一定實在三點之後了,而究竟是三點一刻還是三點一秒,沒有人知道。
那該如何處理這個問題呢?如果仔細思考時鐘在分散式系統中發揮的作用,就會發現其實並不需要一個真的顯示當前時間的時鐘。在分散式系統中,時鐘的作用是為了記錄不同節點之間發生事件的順序,而順序某種程度上可以反應事件之間的因果關係。明白了這個道理,時鐘其實也不必非得是年月日時分秒的形式,只記錄事件發生的順序,就只需要為每一個事件分配一個序號即可。
最先意識到這個問題的是分散式系統領域的大咖Leslie Lamport。他是第一個提出在分散式系統中使用邏輯時鐘(Logic Clock)來解決系統全域性事件排序的問題的人。Lamport提出,在分散式系統的不同節點中發生的事件,在節點內是有著天然的順序的(偏序),但是這個順序不能推廣到整個分散式系統中,節點之間既然需要相互協作,就必須知道節點內發生的事件在整個分散式系統中的順序(全序)。然而前文已經描述過在一個非同步系統中引入一個統一的時鐘是行不通的。因此,Lamport則提出使用一種邏輯時鐘來為事件標記順序。
Lamport提出的方法也很簡單,既然不能用絕對的物理時鐘給事件排序,那就直接為事件賦予一個編號就可以了,節點之間進行通訊的時候,只需要把當前的編號告知通訊的對方即可。
具體的方式是這樣的,在一個節點內發生事件的順序是已知的,可以使用遞增的編號來標記,當涉及到多個節點之間的通訊時,傳送訊息的一方就需要把自己傳送訊息這個事件的編號告知接收訊息的一方。顯然,接收訊息的一方接收訊息這個事件的編號會有兩種情況,一種是小於或者等於收到的事件的編號,一種則是大於。在邏輯上,一個訊息只有先傳送出去才能被接收到,所以當一個節點收到一個訊息,發現收到的訊息所攜帶的事件的編號大於自己當前的事件的編號,那麼這顯然是違反邏輯的,於是就需要把自己當前的時間編號設定為大於收到訊息的編號,使之符合邏輯。這也不會引起節點內部事件順序的混亂,因為改變編號的行為仍然是隻增不減。如果所有的節點都遵循這樣的準則,那麼整個分散式系統中,只要涉及到通訊的事件就一定有一個唯一的順序,而至於那些沒有參與到通訊中的事件(併發事件),其實它們的全域性順序並不重要,因此只需要在節點內部保持遞增即可。
上述過程中的事件的編號,就是大名鼎鼎的Lamport時鐘。Lamport時鐘保持了事件發生的因果關係,同時也知道了整個系統中事件的相對順序。Lamport提出的邏輯時鐘解決了分散式系統內部的事件全序問題,但是細心的讀者會發現,這種時鐘其實只能保證參與了節點之間通訊的事件的順序,而對於沒有參與通訊的併發事件,我們仍然不知道兩個併發事件到底誰先發生誰後發生。其實就滿足分散式系統的各個節點能夠相互協作的需求而言,Lamport時鐘就已經足夠了,然而識別併發事件涉及到資源分配和排程等一系列問題,也是分散式系統中的一大課題,此時就需要用到Cristian等人提出了向量時鐘(Vector Clock)。向量時鐘可以用來識別分散式系統中的併發事件。
在向量時鐘下,每個節點應當儲存一個時鐘向量,向量中的每個元素是當前節點已知的,每個節點最後一個事件的序號,因此向量的大小也應該是系統中參與通訊的節點的數量。在通訊過程中,每個節點之間傳送和接收的時間戳,也不再是Lamport提出的事件序號,而是一個時鐘向量,然後按照Lamport提出的更新原則更新這個向量,即對於時鐘向量的每一個分量,將其更新為當前的最大值。有了這個時鐘向量,我們可以透過比較兩個時鐘向量分量的大小來判斷兩個事件之間的關係,如果一個時鐘向量的所有分量都大於另一個,那麼這兩個時鐘向量之間就是具有因果關係的。只要有任何一個分量小於另一個向量中對應位置的分量,那麼這兩個事件就是併發的。
分散式時鐘是分散式系統的基礎,無論是Lamport時鐘還是向量時鐘,其意義不僅是解決了分散式系統中的基本問題,其設計的基本思路也是分散式系統中核心的思維方式之一。
總結
- 為什麼不能引入統一時鐘;
- 同步系統和非同步系統;
- Lamport時鐘和向量時鐘