Paxos是用於一種分佈式系統而且具備容錯性的一致性算法,是目前業界公認能解決分佈式系統一致性的問題算法之一。 Paxos於1990年由Lamport提出,但直到1998才正式被計算機科學界接受。由於Lamport剛開始採用描述故事的方式來描述算法,但不被相關的出版社接受。後來Lamport發表《Paxos Make Simple》論文使得Paxos加速被廣大計算機工程師理解並接受。但一直以來計算機的工程師抱怨Paxos算法是難以理解、晦澀,本人在學習之初也有深有體會,因此寫下這篇文章總結我的對Paxos算法理解,也供你們參考。算法
Paxos算法的核心問題是:解決分佈式系統的一致性的問題,全部問題均圍繞着在分佈式環境達成一致性而展開討論的。Paxos算法爲了達成一致性,算法就必須保證其安全性和活性。安全
安全性:只有被提出的提案才能被選定,而且只有一個提案被選定。性能優化
活性:最終保證會有一個提案被選定。異步
安全性和活性的組合結果就是:最終有且只有一個被提出的提案被選定。分佈式
在推導算法過程當中,咱們定義了一些角色及名詞。性能
proposer:提案者,它能夠提出一個提案。學習
acceptor:提案的受理者,有權決定是否它自己是否批准該提案。優化
choose:提案被選定,當有半數以上Acceptor批准該提案時,就認爲該提案被選定了。spa
learner:不參與Paxos提案選定的過程,只在提案被選定時,知道提案結果的角色。blog
proposal:提案,由Proposer 提出。一個提案由一個編號及value造成的對組成,如[m, value],提案的編號必須是全局惟一,value即表明了提案自己的內容。
在具體的執行過程當中,同一個進程可能不止充當一種角色,同一個進程可能在三個角色中互換。如下是關於這三個角色的通訊做如下約定:
系統全部消息均存在延遲、丟失、重複的可能,系統也能夠隨時會重啓。
系統全部的消息不存篡改的問題,也即不存在拜占庭的問題。
P1:一個acceptor必須批准(accept)它收到的第一個提案。
這個需求引出一個問題,就是這個若是有多個提案被不一樣的proposers同時提出,這可能會致使雖然每一個acceptor都批准了一個提案,可是沒有一個提案是由多數人都批准的,也就是沒有提案能夠選定的。
上圖都知足p1需求,但沒法選定一個提案。 p1再加上一個提案被選定須要由半數以上的acceptor批准的需求暗示着一個acceptor必須可以批准(accept)不止一個提案。咱們爲每一個提案設定一個編號來記錄一個acceptor批准的那些提案。爲了不混淆,須要保證不一樣的提案具備不一樣的編號。當一個具備某value值的提案被半數以上的acceptor批准後,咱們就認爲該value被選定了。此時咱們也認爲該提案被選定了,提案是由[編號,value]組成。
P2.若是提案的值爲[m,v]被已選定,那全部編號大於m的提案的值必須是v。
由於編號是全序的,條件P2就保證了只有一個Value值被選定的這一關鍵安全性屬性。被選定的提案,首先必須被至少一個Acceptor批准,所以咱們能夠批准知足以下條件來保證P2。
P2a. 若是具備value值v的提案被選定了,那麼全部比它編號更高的被acceptor批准的提案的value值也必須是v。
咱們仍然須要p1來保證提案會被選定。可是由於系統通訊是異步的或者延遲,一個提案可能會在某個acceptor還未收到任何提案時就被選定了。假設一個新的proposer 啓動了,而後產生了一個具備另外一個value值的更高編號的提案,根據p1,就須要c批准這個提案,可是這與P2a矛盾。所以若是要同時知足p1和P2a,須要對P2a進行強化:
P2b.若是具備value值v的提案被選定,那麼全部比它編號更高的被proposer 提出的提案的value值也必須是v。
一個提案被acceptor批准以前確定要由某個proposer 提出,所以P2b就隱含了P2a,進而隱含了P2。因此,只要論證P2b成立就能夠了。
假設[M0,V0]值V0的提案被選定了,須要證實任何具備編號Mn(Mn>M0)的提案的Value值爲V0。
爲了發現如何保證P2b,咱們來如何證實它成立。咱們假設某個提案[M0,V0]提案被選定了,須要證實任何具備編號Mn(Mn>M0)的提案值都爲V0。也就是咱們須要批准數學概括法證實:假設M0至Mn-1提案的Value值都是V0,證實Mn提案的Value值也是V0。由於編號爲M0的提案已經被選定了,這意味着確定存在一個由半數以上的acceptor組成的集合C,C中的每一個acceptor都批准了這個提案。再結合概括假設,M0被選定意味着:
C中的每一個acceptor都批准了一個編號在M0至Mn-1之間的提案,而且每一個編號在M0至Mn-1之間的被acceptor批准的提案都具備value值V0。
任何包含半數以上的acceptor的集合S都至少包含C中的一個成員,咱們能夠批准維護以下不變性就能夠保證編號爲Mn的提案value 值爲V0:
P2c.對於任意的Mn和Mv,若是編號爲Mn和value值爲Mn的提案被提出,那麼確定存在一個由半數以上的acceptor組成的集合S,知足如下二個條件的任意一個:
只要一直保證P2c的正確性,就能夠知足P2b了。從P2開始至P2C,是對提案一系列條件的逐步增強的過程。如今咱們只須要證實P2c的正確性就能夠了。
咱們再看P2c,實際上P2c規定了每一個proposer 如何產生一個提案,對於產生的每一個提案[Mn,Mv]須要知足這個條件「存在一個由超過半數的acceptor 組成的集合S:要麼S中沒有人批准(accept)過編號小於 n 的任何提案,要麼S的任何acceptor批准的全部提案(編號小於Mn)中,Mv是編號最大的提案的決議」。當proposer 遵照這個規則產生提案時,就能夠保證知足P2b。下面咱們反過來看,證實P2c能夠保證P2b,採用數學概括法證實,便是第二數學概括法。
首先假設提案[M0,V0]被選定了,設比該提案編號大的提案爲[Mn,Vn],咱們須要證實的就是在P2c的前提下,對於全部的提案[Mn,Vn],有V0=Vn。
第一步:當Mn=M0+1時,若是有這樣編號的提案,首先咱們知道[M0,V0]被選定了,這樣就不可能存在一個S且S中acceptor批准太小於Mn的提案[S與批准[M0,V0]的acceptor集合確定有交集],那Mn只能是多數集S中編號小於Mn的最大編號的那個提案的值了,此時Mn=M0+1,理論上小於Mn的最大的編號確定是M0,同時因爲S和批准[M0,V0]的acceptor集合都是多數集,就保證了兩者確定有交集,這樣proposer 在肯定Mn取值時,確定選到就是V0。
上面實際上就是數學概括法的第一步,確切的說是使用的是第二數學概括法證實了第一步。上面是第一步,驗證了某個初始值成立。下面,須要假設編號在[M0+1,Mn-1]區間內成立,並在此基礎上推出Mn上也成立。
第二步:根據假設編號在[M0+1,Mn-1]區間內的全部提案value都具爲V0,須要證實的是編號爲Mn的提案值爲V0。根據P2c,首先一樣的不可能存在一個S且S中沒有人批准太小於Mn的提案,那麼編號爲Mn的Value值,只能是一個多數集S中編號小於Mn的最大編號的那個提案的值,若是這個最大編號落在[M0+1,Mn-1]區間內的,那麼值確定是V0,若是不是落在[M0+1,Mn-1]區間,那麼它的編號確定就是M0了,不可能比M0再小了,由於S也確定會與批准[M0,V0]的acceptor集合確定有交集,那麼它的編號值就不會比M0小,而編號若是是M0那麼它的值也是V0。由此得證。
以上這個證實過程使用第二數學概括法,不少人理解不了Paxos的原理很大部分緣由,就是不理解這個證實過程。其實,只要緊扣第P2C中的兩個約束條件,多讀幾遍也就理解了。
爲了維護P2c的不變性,一個proposer 在產生編號爲Mn的提案時,必需要知道某一個將要或已經被半數以上acceptor批准的編號小於Mn的最大編號的提案。獲取那些已經被批准的提案很簡單,只要經過問詢就能夠實現了。proposer 會請求acceptors不要再批准任何編號小於Mn的提案。這就致使了以下的提案生成算法:
以上的流程是Paxos的proposer 的提出提案的處理邏輯,也是Paxos難理解、晦澀的方面,也是算法的重點和難點。我我的的學習經歷的是P2b和P2c的證實和銜接比較難理解,特別是P2c的數學概括證實那塊邏輯。
acceptor批准提案
從上面的內容能夠看到,一個accpetor可能接到二種請求,分別是:prepare和accept請求。咱們已經描述了proposer 的算法。那麼acceptor端呢?它能夠接收兩種來自proposer 的請求: prepare請求和accept請求。acceptor能夠忽略任何請求而不影響算法的安全性。任什麼時候候acceptor能夠迴應accept請求,並批准提案,當且僅當它沒有承諾或批准一個更高編號的提案。能夠推導出增強條件P1a:
P1A. Acceptor能夠批准一個編號爲Mn的提案,當且僅當它沒有迴應過一個編號大於Mn的Prepare請求。
P1a包含了P1,是P1增強條件。固然,acceptor能夠忽略任何prepare和accept請求,也能夠延遲和丟失響應,這些均不會影響Paxos的算法的安全性。
如今咱們獲得了一個完整的提案選擇算法,並知足咱們要求的安全屬性是基於提案編號上全局惟一的。如今咱們作一些簡單優化就能獲得最終算法:
假設一個acceptor接收到一個編號爲Mn的prepare請求,可是它已經迴應了一個編號大於Mn的prepare請求。因而acceptor就沒有必要回應這個prepare請求了,由於它不會批准這個編號爲Mn的提案。它還能夠忽略已經批准過的提案的prepare請求。
有了這些優化,acceptor只須要保存它已經批准的最高編號的提案(包括編號和決議),以及它已經迴應的全部prepare請求的最高編號。由於任何狀況下,都須要保證P2C,acceptor必須存儲這些信息,包括失效並重啓以後。
結合proposer 和acceptor的行爲,咱們將把算法能夠分爲兩個階段來執行。
階段1.
階段2.
proposer 能夠提出多個提案,只要它遵循上面的算法。它能夠在任什麼時候刻放棄一個提案。(這不會破壞正確性,即便在提案被放棄後,提案的請求或者回應消息纔到達目標)若是其它的proposer 已經開始提出更高編號的提案,那麼最好能放棄當前的提案。所以,若是acceptor忽略一個prepare或者accept請求(由於已經收到了更高編號的prepare請求),它應該告知proposer 放棄提案。這是一個性能優化,而不影響正確性。
咱們很容易構建這樣一個場景,兩個proposer 持續發送比對方的更高提案編號,而且最終它們二者沒有一個被選中。例如:proposer -pn提出 pn1完成了phase 1。另外一個proposer -qn接着批准了qn2 > pn1完成了phase 1。proposer -pn在phase 2的以pn1標記的accept request會被全部acceptor拒絕,由於acceptor已經承諾不接受任何number小於qn2的提案。所以proposer-qn開始用新的pn3 > pn2來開始並完成phase 1,而這又致使了Proposer qn2在phase 2的accept被忽略。如此反覆進行。
爲了保證流程的執行,咱們必須選出一個主proposer,做爲提案的惟一人選。若是主proposer能和大數派的集合進行通訊,而且使用了一個比全部已經批准的提案號都大的編號,那麼它就能成功產生被接受的proposal。批准拒絕已有的提案而且批准更高的提案號,主proposer最終會選擇到一個足夠大的提案號,最終使用提案被選定,從而達到數據一致性的目的。