詳解PAXOS演算法1
Paxos演算法的難理解與演算法的知名度一樣令人敬仰,從我個人的經歷而言,難理解的原因並不是該演算法高深到大家智商不夠,而在於Lamport在表達該演算法時過於晦澀且缺乏一個完整的應用場景。如果大師能換種思路表達該演算法,大家可能會更容易接受:
- 首先提出演算法適用的場景,給出一個多數讀者能理解的案例
- 其次描述Paxos演算法如何解決這個問題
- 再次給出演算法的起源(就是那些希臘城邦的比喻和演算法過程)
Lamport首先提出演算法的起源,在沒有任何輔助場景下,已經讓很多人陷於泥潭,在滿腦子疑問的前提下,根本無法繼續接觸演算法的具體內容,更無從體會演算法的精華。本文將換種表達方法對Paxos演算法進行重新描述。
我們所有的描述都假設讀者已經熟讀了Lamport的paxos-simple一文,因此對各種概念不再解釋。
除了Lamport的幾篇論文,對Paxos演算法描述比較簡潔的中文文章是:http://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95,該文翻譯的比較到位,但在關鍵細節上還是存在一些歧義和一些對原文不正確的理解,可能會導致讀者對Paxos演算法更迷茫,但閱讀該文可以快速地對Paxos演算法有個大概的瞭解。
1.應用場景
(1)分散式中的一致性
Paxos演算法主要是解決一致性問題,關於“一致性”,在不同的場景有不同的解釋:
- NoSQL領域:一致性更強調“能讀到新寫入的”,就是讀寫一致性
- 資料庫領域:一致性強調“所有的資料狀態一致”,經過一個事務後,如果事務成功,所有的表資料都按照事務中的SQL進行了操作,該修改的修改,該增加的增加,該刪除的刪除,不能該修改的修改了,該刪除的沒刪掉;如果事務失敗,所有的資料還是在初始狀態;
- 狀態機:在狀態機中的一致性更強調在每個初始狀態一致的狀態機上執行一串命令後狀態都必須相互一致,也就是順序一致性。Paxos演算法中的一致性指的就是這種情況,接下來我們會對這種場景進一步討論。
(2)MQ
假如所有系統的Log資訊都寫入一個MQ Server,然後通過MQ把每條Log指令發非同步送到多個Log Server寫入檔案(寫入多個Log Server的原因是對Log檔案做備份以防資料丟失),則所有Log Server上的資料肯定是一致的(Log內容及順序完全相同),因為MQ本身就有排序功能,只要進了Q資料也就有了序,相當於編了全域性唯一的號,無論把這些資料寫入多少個檔案,只要按編號,各檔案的內容必定是一致的,但一個MQ Server顯然是一個單點,如果宕機,會影響整個系統的可用性。
(3)多MQ
要解決MQ單點問題,首選方案是採用多個MQ Server,即使用一個MQ Cluster,客戶端可以訪問任意MQ Server,不同的客戶端可能訪問不同MQ Server,不同MQ Server上的資料內容、順序可能不一致,如果不解決這個問題,每個MQ Server寫入Log Server的內容就不一致,這顯然不是我們期望的結果。
(4)NoSQL中的資料更新
一般的NoSQL都會通過資料複製的形式保證其可用性,但客戶端對多資料進行操作時,可能會有很多對同一資料的操作傳送的某一臺或幾臺Server,有可能執行:Insert、Update A、Update B....Update N,就一次Insert連續多次Update,最終複製Server上也必須執行這一的更新操作,如果因為執行緒池、網路、Server資源等原因導致各複製Server接收到的更新順序不一致,這樣的複製資料就失去了意義,如果在金融領域甚至會造成嚴重的後果。
上面這些不一致問題性正是Paxos演算法要解決的,當然這些問題也不是隻有Paxos能解決,在沒有Paxos之前這些問題也得到了解決,比如通過使用雙Master模式的MQ解決MQ單點問題;通過使用Master Server解決NoSQL的複製問題,但這些解決方法都存在一些缺陷,要麼難水平擴充套件,要麼影響可用性。當然除了Paxos演算法還有其他一些演算法也試圖解決這類問題,比如:Viewstamped Replication演算法。
上面描述的這些場景的共性是希望多Server之間狀態一致,也就是一致性,再看中文Wiki開篇提到的:
在一個分散式資料庫系統中,如果各節點的初始狀態一致,每個節點都執行相同的操作序列,那麼他們最後能得到一個一致的狀態。為保證每個節點執行相同的命令序列,需要在每一條指令上執行一個“一致性演算法”以保證每個節點看到的指令一致
大家或許會對該描述有更深的理解。
2.Paxos如何解決這類問題
Paxos對這類問題的解決就是試圖對各Server上的狀態進行全域性編號,如果能編號成功,那麼所有操作都按照編號順序執行,一致性就不言而喻。當Cluster中的Server都接收了一些資料,如何進行編號?就是表決,讓所有的Server進行表決,看哪個Server上的哪個資料應該排第一,哪個排第二...,只要多數Server同意某個資料該排第幾,那就排第幾。
很顯然,為了給每個資料唯一編號,每次表決只能產生一個數據,否則表決就沒有任何意義。Paxos的演算法的所有精力都放在如何在一次表決只產生一個數據。再進一步,我們稱表決的資料叫Value,Paxos演算法的核心和精華就是確保每次表決只產生一個Value。
3.Paxos演算法
我們對原文的概念加以補充:
- promise:Acceptor對proposer承諾,如果沒有更大編號的proposal會accept它提交的proposal
- accept:Acceptor沒有發現有比之前的proposal更大編號的proposal,就批准了該proposal
- chosen:當Acceptor的多數派都accept一個proposal時,該proposal就被最終選擇,也稱為決議
也就是說,Acceptor對proposer有兩個動作:promise和accept
下面的解釋也主要圍繞著”Only a single value is chosen,“,再看下條件P1,
P1:An acceptor must accept the first proposal that it receives.
乍一看,這個條件是顯然的,因為之前沒有任何value,acceptor理所當然地應該accept第一個proposal,但仔細想想,感覺P1這個條件很不嚴格,到底是一個對問題的簡單描述還是一個數學上嚴格的必要條件?這些疑問歸結為2個問題:
(1)這個條件本質上在保證什麼?
(2)第二個proposal怎麼辦?
在後續的演算法中看到一個Acceptor是否批准一個Value與它是否是第一個沒有任何關係,而與這個Proposal的編號有關。那豈不說明P1沒有得到保證?開始我也百思不得其解,後來經過跟朋友交流發現,P1中的"accept"其實是指acceptor對proposer的"promise",就是語言描述跟演算法的步驟描述之間存在歧義,因此我認為對演算法問題還是應該採用數學語法而非文字語言。
所以,P1是強調了第一個proposal要被promise,但第二個還未提到,這也是疑問之一。
也很顯然的是,單靠P1是無法保證Paxos演算法的,因可能無法形成多數派,那接下來的討論應該是考慮如何彌補P1的缺點,使其可以保證Paxos演算法,就是我們希望未來的條件應該說明:
- 如何解決P1中無法形成多數派的問題
- 第二個proposal如何選擇
於是約束P2出現了:
P2:If a proposal with value v is chosen, then every higher-numbered proposal that is chosen has value v.
P2的出現讓人大跌眼鏡,P2並沒沿著P1的路向下走,也沒有解決P1的上述2個不完備,而是從另一個側面討論如何保證只能選出一個Value。P1討論的是該如何選擇,P2討論的是一旦被選出來,之後的選擇應該不變,就是P1還在討論選的問題,P2已經選出來了,中間有個斷層,怎麼選的沒有討論。
其實從後面Lamport不斷對P2增強可以看出,P2裡面蘊含著P1(通過proposal編號,第一次之前沒有編號,所以選擇),P2才真正給出了怎麼選擇的具體過程,從事後分析看,P1給出了第一個該怎麼選,P2給出了所有的該怎麼選,條件有點重複。所以,把P1和P2看作是兩個獨立條件的做法是不準確的,因而中文wiki中提到“如果 P1 和 P2 都能夠保證,那麼約束2就能夠保證”,對細微理解有一定的影響。
也不是說P1就沒有用,反過來看,P2是個未知問題,而P1是這個未知問題的已知部分,從契約的角度來看,P1就是個不變式,任何對P2的增強都不能過了頭以至於無法滿足P1這個不變式,也就是說,P1是P2增強的底線。
那還有沒有其他的不變式需要遵守?是否在對P2增強的過程中已破壞了這些未知的不變式?這些高難度的問題牽扯到Paxos演算法正確性,要看MIT的嚴格的數學證明,已超出了本文。
另外,中文Wiki對P2的描述是:“P2:一旦一個 value 被批准(chosen),那麼之後批准(chosen)的 value 必須和這個 value 一樣。”,原文采用higher-numbered更能描述未來對proposal進行編號這個事實,而中文采用“之後”,已經完全失去這個意義。
我們暫時按下P1不表,近距離觀察一下P2,為了保證每次選出一個value,P2規定在一個Value已經被選出的情況下,如果還有其他的proposer提交value,那之後批准的value應該跟前一個一致,就是在事實上已經選定一個value時,之後的proposer不能提交不同的value把之前的結果打亂。這是一個泛泛的描述,但如果這個描述能得到實現,paxos演算法就能得到保證,因此P2也稱"safety property"。
接下來的討論都時基於“If a proposal with value v is chosen”,如何保證“then every higher-numbered proposal that is chosen has value v”,具體怎麼做到“a proposal with value v is chosen"暫且不談。
P2更多是從思想層面上提出來該如何解決這個問題,但具體的落實工作需要很多細化的步驟,Lamport是通過逐步增強條件的方式進行落實P2,主要從下面幾個方面進行:
- 對整個結果提出要求(P2)
- 對Acceptor提出要求(P2a)
- 對Proposer提出要求(P2b)
- 對Acceptor與Proposer同時提出要求(P2c)
Lamport為什麼能把過程劃分的如此清楚已經不得而知,但從Lamport發表的文章來看,他對分散式有很深的造詣,也持續了很長的時間,能有如此的結果,與他對分散式的基礎與背後的巨大努力有很大關係。但對我們而言,不知過程只知個結果,總感覺知其然不知其所以然。
我們沿著上面的思路繼續:
P2a:If a proposal with value v is chosen, then every higher-numbered proposal accepted by any acceptor has value v.
這個條件是在限制acceptor,很顯然,如果P2a得到了滿足,滿足P2是肯定的,但P2a的增強破壞了P1不變式的底線,具體參考原文,所以P2a本身沒啥意義,轉而從proposer端進行增強。
P2b:If a proposal with value v is chosen, then every higher-numbered proposal issued by any proposer has value v.
這個條件是在限制proposer,如果能限制住proposer,對acceptor的限制當然能被滿足的。同時,因為限制proposer必須提交value v,也就順便保證了P1(第一個肯定是value v)
但P2b是難以實現的,難實現的原因是多個proposer可以提交任意value的proposal,無法限制proposer不能提交某個value,因此需要尋找P2b的等價條件:
P2c:For any v and n, if a proposal with value v and number n is issued, then there is a set S consisting of a majority of acceptors such that either
(a) no acceptor in S has accepted any proposal numbered less than n, or
(b) v is the value of the highest-numbered proposal among all proposals numbered less than n accepted by the acceptors in S.
根據原文,P2c裡面蘊含了P2b,但由P2c推導P2b是最難理解的部分。
首先要清楚P2c要做什麼,因為P2b很難直接實現,P2c要做的就是解決P2b的問題,就是解決“如果value v被選擇了,更高編號的提案已經具有value v”,也就是說:
- R:“For any v and n, if a proposal with value v and number n is issued”是結果,而
- C:“ then there is a set S consisting...”是條件
就是要證明如果C成立,那麼結果R成立,而原文的表達是“如果R成立,那麼存在一個條件R”,容易讓人搞混因果關係,再次感嘆如果使用數學符號表達這樣的歧義肯定會減少很多。
P2c解決問題的思路是:不是直接嘗試去滿足P2b,而是尋找能滿足P2b的一個充分條件,如果能滿足這個充分條件,那P2b的滿足是顯然的。還要強調一點的是proposer可以提交任意的value,你怎麼能限制我提交的必須是value v呢?其實原文中的“For any v and n, if a proposal with value v and number n is issued”是指“如果一個編號為n的proposal提交value v,並且value v能被acceptor所接受”,要想被接受就不能隨便提交一個value,就必須是一個受限制的value,這裡討論的前提是value v是要被接受的。然後我們再看下,是否滿足了條件C,結果R就成立。
(a) no acceptor in S has accepted any proposal numbered less than n
如果這個條件成立,那麼n是S中第一個proposal,根據P1,必須接受,所以結果R成立
(b) v is the value of the highest-numbered proposal among all proposals numbered less than n accepted by the acceptors in S
這個證明先假設編號為n的proposal具有value X被選擇,肯定存在一個集合C,其中的每個acceptor都接受了value X,而集合S中的每個Acceptor都接受了value v,因為S、C都是多數派,所以存在一個公共成員u,既接受了X,又接受了v,為了保證選擇的唯一性,必須X=v.
大家可能會發覺該證明有點不太嚴格,“小於n的最大編號”與n之間還有很多proposal,那些proposal也有一些value,那些value會不會不是v?
這個就會用到原文中的數學歸納法,就是任意的編號m的proposal具有了value v,那麼n=m+1是,根據上面也是具有value v的,那麼向後遞推,任意的n >m都具有value v。中文wiki中的那個歸納證明不需要對m...n-1正推,而對n反證,通過數學歸納正推完全可以得出最終結果。
也就是說,P2c是P2b的一個加強,滿足P2c就能滿足P2b。
我們再近距離觀察下P2c,發現只要在proposer提交提案前,諮詢一下acceptor,看他們的最高編號是啥,他們是否選擇了某個value v,再根據acceptor的回答進行選擇新的編號、value提交,就可以滿足P2c。通過編號,可以把(a)和(b)兩個條件統一在一起。
其實P2c要表達的思想非常簡單:如果前面有value v選出了,那以後就提交這個value v;否則proposer決定提交哪個value,具體做法就是事前諮詢,事中決定,事後提交,也就是說可以通過訊息傳遞模型實現。Lamport通過條件、集合、歸納證明等形式表達該問題,而沒提這樣做的目的,會導致理解很困難。大家可能會比較疑惑,難道自始至終只能選出一個value?其實這裡的選出,是指一次選舉,而不是整個選舉週期,可以多次執行paxos,每次都只選出一個value。
滿足P2c從側面也反映出要想提交一個正確的value v,要對proposer、acceptor同時進行限制,僅限制一方問題是無法解決的。
再回顧下條件之間的遞推關係P2c=>P2b=>P2a=>P2,就是說P2c最終保證了P2,也就是解決了如何做到一個value v被選擇之後,被選擇的編號更大的proposal都具有value v,P2c不僅保證P2的結果,更提出了“如何選”的問題,就是上面分階段進行,這就填補了P1與P2之間缺少如何選的斷層,還有P1的2個不完備問題從直觀上感覺會得到解決,具體的要看演算法過程章節。
P1的不完備問題:
P2c也順便解決了P1的不完備問題,因為proposer提交的value是受acceptor限制的,就不會在一次選舉中提交兩個不同的value,即使能提交也會因為proposal編號問題有一個會被拒絕,從而能保證能形成多數派。
另一個關於第二個該怎麼選的不完備問題,也是顯然的了。
再次證明了,P2裡面蘊含了P1,P1只是未知問題P2的不變式。