最近在做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真正的狀態的(忙於其他任務還是真的掛掉了),也就是說我們是無法做到完全正確的錯誤檢測。
這種時候按照上面的說法,有兩種選擇,
- 任務B依舊或者,無限重試,不斷等待。
- 直接認為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算法,那就等到下一節再說吧。
總結:
- 分布式一致性指的其實就是分布式異步環境下,要讓多個節點對系統狀態的改變能夠達成共識。
- 分布式系統一致性難,難在異步通信不可靠。由此衍生出了liveness和satefy取舍的問題以及僵屍節點問題,有了FLP impossibility定理。
- paxos/raft等算法通過一個安全時間段,可以在某種程度上實現分布式系統的一致性。
PS:本篇大部分參考自端到端一致性,流系統Spark/Flink/Kafka/DataFlow對比總結,只是里面有很多是講流處理系統的。不過同樣是裂牆推薦,反正看過的都說好。
以上~