1. 程式人生 > >MIT 6.824拾遺(一)聊聊basic-paxos

MIT 6.824拾遺(一)聊聊basic-paxos

前言 ====== > The Paxos algorithm, when presented in plain English, is very simple. > —————— Lamport,《Paxos Made Simple》 > > Unfortunately, Paxos is quite difficult to understand, in spite of numerous attempts to make it more approachable. > —————— Diego Ongaro,《In Search of an Understandable Consensus Algorithm》 在不同的部落格和視訊中,對於basic-paxos的講解有多個維度。例如說zhihu作者@多顆糖和Diego Ongaro(Raft演算法的提出者)的視訊是以*具體事例*作為角度來討論和分析的,而Lamport的《Paxos Made Simple》和Paxos的Wikipedia則通過逐級新增和強化`約束條件`的方式,來逐步使一個簡單的演算法漸漸向共識演算法靠攏,最終推匯出basic-paxos的實現邏輯。我個人比較推薦的是首先參照Diego Ongaro和 @多顆糖 給出的註解來學習和了解basic-paxos的基本邏輯,再將自己想到的和鎖提到的各種情景套入到演算法中,推斷這些情景的存在性、產生過程和解決方法,最後閱讀Lamport的論文。 本篇blog更傾向於做一個關於《Paxos Made Simple》中關於basic-paxos的個人註解。參考的資料我會放在部落格的最末尾。 共識問題 =========== #### 共識 在一個系統中包含有n個程序{1,2,3,4...n},部分程序會提出一個VALUE,程序之間互不相知對方的VALUE到底是多少,但可以通過*相互通訊*來獲悉其他程序的VALUE,也可以考慮修改自己的VALUE;我們需要設計一種演算法,使得這n個程序最終能夠協商達成一個不可撤銷的最終VALUE;能達成這個目的的演算法就被稱作是一種*共識演算法*。 #### 非同步網路條件 程序之間必須通過相互通訊才能獲悉對方的VALUE,但通訊過程是*完全非同步*的。如果程序1希望與程序2通訊,那麼程序1就要向程序2發出請求,然後等待程序2的答覆;在非同步網路環境下,這個等待的時間可能是無限的。 #### 非拜占庭條件 程序之間的非同步通訊可能丟失、重複、失序、延遲,通訊網路之間也可能會發生分割槽(但分割槽最終會復原),但通訊的內容必定不會損壞(corrupt); 程序本身也隨時可能崩潰、重啟,但程序一旦開始執行,必定處於正確的狀態。在程序間的通訊中,程序的迴應必定能正確反映自己的狀態,即程序不會對其他程序進行無意(進入了錯誤狀態但不自知)或者惡意的(被黑客篡改、偽造了通訊內容)欺詐; 對於一個共識演算法來說,它的執行必須滿足如下三個性質: 1)終止性(Termination),所有的程序最終都會認同某一個值; 2)協定性(Agreement),所有程序最終認定的必定是同一個值; 3)完整性(Integrity),如果正確的程序都提議同一個值,那麼所有處於認同狀態的程序都選擇該值; 由於我們假定不存在拜占庭問題,因此只要共識演算法只需滿足1)和2)即可。 共識(consensus)問題是分散式系統研究中的基礎問題之一,雖然與解決了多複製狀態機間狀態一致性問題的raft演算法相比,basic-paxos所解決的*共識問題*看起來更偏向底層、難以直接應用,但其同樣可以解決分散式系統中的一系列核心問題。 在理解了basic-paxos執行的環境(非同步非拜占庭),以及basic-paxos所需要解決的問題之後(達成共識,並滿足三個共識演算法的性質),我們可以開始討論basic-paxos的實現了。 paxos的基本概念 =========== 在一個paxos系統中存在三類角色:proposers(提議者)、acceptors(接收者)、learners(學習者)。由proposer提出VALUE,由acceptor表決是否接受(accept)proposer提議的VALUE,**共識(consessus)的達成等價於acceptors們對VALUE達成一致共識**,learner只能被動的學習acceptor所達成共識的VALUE; 舉例來說,系統中有2個proposer,5個acceptor,2個learner;我們現在希望系統對Colors = {RED,ORANGE,YELLOW,GREEN,BLUE,BLACK,PURPLE} 中的一種Color達成共識;那麼需要由proposer發出(**issue**)提議,希望acceptor們能接受(**accept**)它所提議的Color(兩個proposer可能提議不同的顏色,單獨的一個proposer也可能中途改變主意,發出另一種顏色的提議);當acceptor能夠對COLOR達成共識時,便選出(**chosen**)了一個不可撤銷的Color(假設為YELLOW);acceptor間達成共識的結果應當被learner所學習到; 等價性 ----------- 不難理解,前文所述的共識演算法的三點特性,等價於paxos演算法中的以下三個特性: 1)決議(VALUE)必須由proposer提出; 2)如果acceptor達成了共識,那麼它們認定的VALUE**不可撤銷**; 3)learn只能學習到那些達成共識的VALUE; 也就是說,我們paxos演算法只要保障了以上三個特性,便保證了共識演算法需要保證的特性,那麼我們的paxos演算法就是一種共識演算法;這樣將原本比較抽象的問題轉換成了比較具體的問題;我們在上文中又提出了三個新概念:accept、chosen、issue: accept -------- accept指一個acceptor暫時接受了一個VALUE,但不保證這個VALUE是最終達成共識的VALUE;當acceptor收到了新的提案時,它可能會修改自己所accept的VALUE。 chosen --------- 當超過半數的acceptor成功的accept了一個VALUE之後,我們稱這個VALUE已經被選中(chosen)了,這個VALUE就是**最終必須要達成共識的VALUE**,而這也意味著我們可能會修改acceptor所accept的VALUE,讓它變成對應的被chosen的VALUE。 issue --------- 一切的VALUE必須先由proposer釋出(issue),然後才可能被一些acceptor所接受(accept),然後才可能有機會最終被選中(chosen)。如果一個VALUE沒有被proposer所釋出,那麼這個VALUE就不可能被選中(chosen)。 值得注意的是,在Paxos中,一臺機器可以同時擔任三種角色中的多個角色。 在定義了上述內容之後,我們終於可以逐漸推導basic-paxos演算法了。我們首先討論最簡單的情景與解決這一情景的簡單演算法,再逐漸強化我們的演算法以推廣到更加複雜的情景,最終得到basic-paxos演算法。 約束條件P1 =============== Lamport首先假定了一個非常簡單的情景:網路條件良好(請求和應答都是立即送達的)且機器不會崩潰,只issue了一個提案且這個提案只會被髮布一輪,自然也就只有一個VALUE被提出;我們期望我們的演算法在這個情景下**必定**能chosen那個VALUE;為此引入了約束條件P1: > **P1. An acceptor must accept the first proposal that it receives.** 容易證明,在上述情景下,如果演算法滿足P1,則必定能chosen一個VALUE,即P1在上述情景下是**完備**的。但如果把情景稍微複雜化,則P1就不完備了:我們假定多個proposer們issue了提案,這些提案涉及到了不同的VALUE,且存在一定的網路延遲,且proposer們只issue一輪提案;根據P1,acceptor們根據請求到來的先後順序選擇了VALUE,但可能沒有一個VALUE被半數以上的acceptor所accept,即**沒有一個VALUE被chosen**,如下圖所示: ![](https://img2020.cnblogs.com/blog/2028256/202103/2028256-20210320215757418-604014799.png) 由於proposer們只會釋出一輪提案,因此acceptor們永遠不會改變自己所accept的VALUE,也就永遠不會有VALUE被chosen,這樣就違背了共識演算法的Termination要求; 提案號(ProposeNum)的引入 ============= 通過上述討論我們可以得知,如果僅進行一輪提案,則可能會因為其他proposer與之競爭acceptor導致無法chosen一個VALUE。但上文的情景中,僅僅假定網路只會造成延遲(Delay)而已,更加惡劣的情況(丟失、失序等)還尚未考慮在內。實際上在非同步的情景下,即使一輪提案順利的chosen了一個VALUE,proposer也可能因為acceprot的回覆丟失和延遲而久久無法獲知。因此,**約束條件P1僅在前文中的那個簡單的情景下適用**。現在我們的情景更加複雜了,我們需要*尋找更多的約束條件*來使演算法達到要求! 由於一輪提案無法保證chosen一個VALUE,因此我們的basic-paxos需要新增**多輪提案**的支援。此時我們的演算法希望能夠保證,**無論我的提案會進行多少輪,只要我最終能chosen一個不可撤銷的VALUE即可。** 為了區分開不同的提案以及不同輪次的提案,我們需要為每一個提案分配一個**唯一的、單調遞增的proposeNum**,並將這個proposeNum捎帶在通訊的資訊中。最簡單的proposeNum的生成方法為 `concatenate(物理or邏輯時鐘,proposerId)`,即將物理or邏輯時鐘作為proposeNum的高位,將proposerId作為proposeNum的低位。由於物理or邏輯時鐘是嚴格遞增的,因此proposeNum是單調遞增的;由於proposerId是唯一的,因此proposeNum必定是唯一的。至於proposeNum的具體應用,將在下面的內容中進行講解。 ![](https://img2020.cnblogs.com/blog/2028256/202103/2028256-20210320223517892-1402060797.png) 上圖為Diego Ongaro在講解Paxos的視訊中使用的ProposerNumber的生成方式,Round Number本身可以看做是一個邏輯時鐘,類似於Raft演算法中的term。 再談約束條件P1 =========== 我們回憶一下P1的內容: > P1. An acceptor must accept **the first proposal** that it receives. 在Lamport為提出P1所舉的例子中,限定只進行一輪提案,因此the first proposal的指代是非常明顯的,但在引入了proposeNum之後,**the first proposal**的指代就不是很明確了,Lamport在《Paxos Made Simple》中也沒有很明確的定義在引入了proposeNum下的 **the first proposal** 到底是什麼。 我個人根據上下文的是這樣理解的:**假設無法保證proposer們提案的proposeNum不同**,那麼存在不同的proposer通過同一個proposeNum提出不同VALUE的情景;對於這些相同proposeNum但不同VALUE的多個提案,acceptor只能accept它所收到的第一個提案,並拒絕相同proposeNum其他的VALUE;但由於proposeNum是全域性唯一、僅用一次的、僅能由唯一的proposer生成的,因此一旦proposeNum給定,那麼這個提案的VALUE就已經確定了,因此不存在前文中我所提到的問題。 到此為止,如果是按照我的理解的話,P1條件已經變得非常模糊甚至可以認為不存在了,因為acceptor接受任何proposeNum下的任何VALUE,仍然是滿足P1的。但這沒有關係,在討論完P2之後,我們會強化P1條件,使其成為我們共識演算法的重要組成部分。 約束條件P2 ========= 上文長篇大論的討論的內容濃縮起來其實很簡單:**在非同步、非拜占庭條件下,共識可能需要一輪以上的提議才能實現**,但多輪提議僅僅是共識演算法的一個*必要不充分條件*,我們還需要加入更強的約束。 引入了多輪提議之後,你可以嘗試著去構想一些情景,你會發現,構建一個多輪提議下仍然無法讓半數以上的acceptor們accept同一個VALUE的情景,實在是太容易太容易了。*怎麼避免他們陷入這種無窮無盡的扯皮,暫時不是我們要考慮的內容*,我們現在要考慮的是另一個同等重要的問題:**如何保障chosen VAUE不可撤銷,或者說能避免chosen到第二個VALUE?** 我們知道,一個VALUE被chosen等價於其被半數以上的acceptor們**accpet**了,所以最簡單的讓chosen VALUE不可撤銷的方法是*禁止acceptor們改變自己accept的VALUE*,但這就違背了P1,也很可能使共識永遠無法達成,因此我們仍然希望**acceptor可以更改自己accept的VALUE**。然而但如果不增加額外的約束限制,那麼仍然可能有多個VALUE被chosen,這就違背了我們共識演算法等價條件的**不可撤銷性**,如下圖所示: ![](https://img2020.cnblogs.com/blog/2028256/202103/2028256-20210320215851148-992785364.png) 一切的罪魁禍首是proposer S5 渾然不知的提出了一個BLUE提案,而acceptor們渾然不知,只是嚴格的遵守著約束條件P1(proposer:怪我嘍?你們acceptor們怎麼不長眼吶);我們要避免這種情況,據此引入了約束條件P2: > **P2. If a proposal with value v is chosen, then every higher-numbered proposal that is chosen has value v.** 如果我們的共識演算法能讓acceptor們遵守P1,讓proposer們遵守P2,雖然proposer和acceptor們可能還會因為遲遲無法讓VALUE達成共識而互飛提案相互扯皮,但只要一個VALUE被chosen了,那麼後續的proposer們issue的提案中的VALUE只能是那個chosen VALUE,這樣就保證了VALUE的不可撤銷性。如果我們引入了一些其他的手段解決了相互扯皮的問題,那麼我們的演算法就是一個**共識演算法**了! 但是想著容易做著難,如何實現P2約束目前來說是一個很棘手的問題。此時,Lamport點明瞭幾個嘗試,試圖使用一個比P2更嚴格但更好實現的約束來解決這個問題。由於這些約束比P2更加嚴格,因此實現了這些約束,也就實現了P2。 P2a約束 ------------- > P2a. If a proposal with value v is chosen, then every higher-numbered proposal accepted by any acceptor has value v. 即我們希望在acceptor處加強限制;如果半數以上的acceptor們已經accept了VALUE,那麼我們期望acceptor在accept更高proposeNum的提案時,只接受那些VALUE為v的提案;暫時不考慮具體的實現方式,不難推理得出,滿足P2a就滿足了P2。 但這又產生了一些問題:第一,一個acceptor很難知道一個VALUE是不是chosen VALUE,這在實現上有一些困難;第二,正如前文所述,只有P1和P2同時滿足時,我們的演算法才是共識演算法,但P1和P2a可能存在衝突。仍然假設我們的系統中有2個proposer,5個acceptor。proposerId為1的proposer兢兢業業的讓VALUE = RED 這個VALUE被acceptor {1,4,5}所accept,但由於網路分割槽的緣故,3沒有收到過任何提案。現在我們知道RED已經被chosen了,而渾渾噩噩的proposerId為2的proposer它 issue 了 VALUE = GREEN 的提案,這個提案被遞交到了3上。根據P1,3應當accept了GREEN這個VALUE,但根據P2a,3 accept的提案的VALUE必須是RED,這樣就產生了衝突。 最終由於各種原因,P2a約束沒有被我們所採用,Lamport又點出了第二個約束: P2b約束 -------- > **P2b. If a proposal with value v is chosen, then every higher-numbered proposal issued by any proposer has value v.** 這裡我們將限制條件加在了proposer端,使得實現大為簡化,因為proposer是可以根據proposeNum以及與acceptor之間的通訊得知是否有VALUE被chosen的。雖然從構思上講,P2b的實現已經比較容易了,但我們期望讓它實現的更簡單、更嚴謹、更明確一些,Lamport繼續提出新的約束條件P2c: P2c約束 ----------- > 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約束下,proposer發