1. 程式人生 > >分散式系統一致性問題與Raft演算法(上)

分散式系統一致性問題與Raft演算法(上)

最近在做MIT6.824的幾個實驗,真心覺得每一個做分散式相關開發的程式設計師都應該去刷一遍(裂牆推薦),肯定能夠提高自己的技術認知水平,同時也非常感謝MIT能夠把這麼好的資源分享出來。

其中第二個實驗,就是要基於raft演算法,實現一個分散式一致性系統。但今天先不說raft演算法,而是先討論下什麼是分散式一致性問題,以及為什麼它會難!!下一章再說raft是如何設計從而解決了分散式共識這一難題。

什麼是分散式一致性問題

首先,什麼是分散式系統一致性問題?分散式系統這個詞應該不用多解釋,簡單地說就是由多個節點(一個節點即一臺物理機器或一個程序),非同步得通過網路通訊而組成的系統。

而一致性(Consistency),這個詞在不同的場景下有不同的含義,比方說大家比較熟的應該是在關係型資料庫中事務的一致性。但在分散式系統中,一致性的基本含義是對進行進行一個或多個操作指令之後,系統內的其他成員對這些操作指令的結果能夠達成共識,也就是對結果的看法是一致的。

舉個例子,比方說有多個客戶端,這些客戶端出於某種原因,都要對分散式系統中的變數v賦值,客戶端A發起一個賦值請求修改v=a,客戶端B也發起一個請求修改v=b,等等等。但最終在某一個時刻布式系統內都能夠對v的值產生一個共識,比如最終大家都知道v=a。而不會說系統內不同節點對這個值有不同的看法。

要做到這一點很難嗎?是的,很難。

注:一致性這個詞其實包含挺廣的,但這裡特質共識這個意思,所以有時候也會用共識的說法,知道是一個意思就行了。

為什麼分散式一致性問題很難

明白了什麼是分散式一致性問題之後,我們再來討論為什麼分散式一致性會很難。

如果是在單機環境下,實現一致性是很容易做到的,基本上我們開發的程式都能夠做到。但在分散式環境下,則難度放大了無數倍。因為在分散式環境下節點間的狀態是通過網路通訊進行傳輸的,而網路通訊是不可靠的,無序的,注意這裡的不可靠不是說tcp或udp的那個可靠,而實無法通過網路通訊準確判斷其他節點的狀態。

比如A向B傳送了一個請求,B沒有迴應。這個時候你沒辦法判斷B是忙於處理其他任務而無法向A回覆,還是因為B就真的掛掉了。

順便說一點,分散式一致的問題往往還具有一定的欺騙性。

它具有一定欺騙性的原因在於分散式一致性的問題直觀感受上往往比較簡單,比如上面的A向B傳送請求的問題,我們無論選擇直接認為B掛掉,或者選擇讓A不斷進行重試,看上去似乎都能解決這個問題。但隨著而來的又會有新問題,比如A選擇認為B掛掉而進行失敗處理,那麼系統繼續無礙執行。但如果B只是因為系統任務繁忙,過一會恢復作業,A就因為自身的選擇破壞了資料的一致性,因為在B斷線期間系統就不一致了。這就又出現了新的問題。

總結起來,就是看似簡單的問題,引入簡單的解,往往又會出現新的問題。而後又繼續在此基礎上“打補丁”,而後又會出現新的問題,不斷迴圈往復,一個簡單的問題不斷疊加,就變成了超級複雜棘手的問題。就像築堤堵水,水不斷漲,堤壩不斷堆砌,最終到了一個誰也沒法解決的境地。

說回剛剛的話題,按照剛剛的例子,其實可以引出另一個問題,那就是活性(liveness)和安全性(satefy)的取捨。

活性(liveness)與安全性(satefy)

活性與安全性,這個要怎麼理解呢?

剛剛說到,當A向B傳送請求,B沒有及時迴應。但這個時候,A是無法準確知道B真正的狀態的(忙於其他任務還是真的掛掉了),也就是說我們是無法做到完全正確的錯誤檢測。

這種時候按照上面的說法,有兩種選擇,

  1. 任務B依舊或者,無限重試,不斷等待。
  2. 直接認為B掛掉了,進行錯誤處理。

選擇1,破壞了系統的活性,因為在重試的時間內,系統是無法對外提供服務的,也就是短暫得失活了。

選2呢又破壞了安全性,因為如果B其實沒有掛掉,而這時候重新啟動一個節點負責原本B的工作,那麼此時系統中就會有舊的B節點,和新的B節點。此時舊的節點就稱之為殭屍節點(Zombie)。而如果在主從分佈的系統,也就是一個leader多個follower的系統中,如果B剛好是leader,那麼這種情況也被稱之為腦裂。

可以發現,liveness和響應速度有關,而satefy又和系統的可用性相關,並且這兩者是不可兼得的。

關於這個問題,上世紀Lynch發表的一篇論文《Impossibility of Distributed Consensus with One Faulty Process》,基本上已經闡述了這個定理,也就是FLP impossibility。

FLP impossibility

FLP impossibility說明了這樣一件事,在分散式或者說非同步環境下,即便只有一個程序(節點)失敗,剩餘的非失敗的程序不可能達成一致性。

這個是分散式領域中的定理,簡稱就是FLP impossibility。

當然所有的定理似乎都不是那麼好理解,FLP impossibility也是一樣,光是結論聽起來就非常拗口。證明起來那就更加抽象,甚至在論文中也沒有通過例子來論證。因為如果要通過例項來論證,那麼首先就得要先設計N多的分散式演算法,然後再逐一證明這些演算法是FLP impossibility。

其實通俗些的理解,那就是說分散式(非同步)環境下,liveness和satefy是魚與熊掌不可兼得。不可能做到100%的liveness同時又兼顧到satefy。想要100%的satefy,那麼liveness又保證不了,這裡面又好像有CAP的影子,不得不說道路都是相通的。

話說回來,既然FLP impossibility已經說死了,非同步環境下,即便只有一個程序(節點)失敗,剩餘的非失敗的程序不可能達成一致性,那麼paxos和raft這些演算法又是如何做到分散式非同步環境下一致的呢?

柳暗花明

其實FLP impossibility已經為我們指明方向了。既然無法完全兼得,那自然要放鬆某方面的限制,satefy是不能放鬆的,那自然只能從liveness上下手了。

具體做法上,那就是給分散式系統加上一個時間限制,其實在前面介紹liveness和satefy的時候,應該就有人能想到了。既然不能一直等待也不能直接任務遠端節點掛掉,那麼折衷一下,在某個時間內不斷重連,超過這個時間,則認為遠端節點是掛掉就可以了。

而事實上也正是如此,如果你對zookeeper熟悉,那應該知道zookeeper在選舉leader的時候是不提供服務的,這就是它喪失部分liveness的一個體現。另一個體現是,效能,因為要通過一個時間段來對遠端節點狀態進行確認,那自然效能會有所下降,這又是不可避免的。

而具體的raft演算法,那就等到下一節再說吧。

總結:

  1. 分散式一致性指的其實就是分散式非同步環境下,要讓多個節點對系統狀態的改變能夠達成共識。
  2. 分散式系統一致性難,難在非同步通訊不可靠。由此衍生出了liveness和satefy取捨的問題以及殭屍節點問題,有了FLP impossibility定理。
  3. paxos/raft等演算法通過一個安全時間段,可以在某種程度上實現分散式系統的一致性。

PS:本篇大部分參考自端到端一致性,流系統Spark/Flink/Kafka/DataFlow對比總結,只是裡面有很多是講流處理系統的。不過同樣是裂牆推薦,反正看過的都說好。