paxos-simple

上週給組裏分享課程,其中講到了paxos,覺得沒講好,遂決定看看paxos論文,看的時候有的枯澀的地方就翻譯到文本里記錄,翻譯得越來越多,索性都翻譯了吧 ...


1 介紹

在實現一個容錯分佈式系統時,人們認爲Paxos算法理解起來很困難,大概對於很多讀者來說最初的陳述是用的希臘語。事實上,它是分佈式算法中最簡單也最明顯的一種。

它本質上是一個共識算法-the “synod” algorithm of。下一節敘述了這種共識算法幾乎不可避免地遵循我們想讓它滿足的屬性條件。最後一節解釋了完整的Paxos算法:通過簡單的狀態機的共識程序來構建一個分佈式系統-這應該是被廣爲人知的,因爲它在分佈式系統理論文章中被引用最多的。

2 一致性算法

2.1 問題

假設有一些進程可以提議values。一個共識算法可以保證在這些提議的values中只有一個value被選中。如果沒有提議任何value,那麼也沒有任何value被選中。

如果一個value被選中了,那麼這些進程應該能夠學習這個被選中value。協商一致的安全性要求如下:

·只能發起提議的value能被選中

       ·只能有一個值被選中

       ·並且一個進程只能獲取到真正被選中的value

我們不用嘗試指定精確的要求。但是目標是保證一些被提議的value最終會被選中,並且當一個value被選中,那麼某個進程最終都能獲取到這個value。

在共識算法中我們使用三個角色由三個種類代理:proposers、acceptors、learners(提議者、接受者、結果學習者)。在一個具體實現中,單個進程可能同時扮演多種代理,,但是從從代理到進程的映射關係我們並不關心。

假設這些代理能夠通過發送消息相互交流。我們使用異步、非拜占庭模型:

       ·這些代理的執行是任意速度的,也可能發生故障而停止,也可能重啓。因爲所有的代理都可能在選中一個value後發生故障並且重啓,因此這些代理必須持久化一些信息,即使發生故障和重啓(也能恢復)

       ·發送的消息可以是任意長度的、可能重複、可能丟失,但是不能被篡改。

2.2 選中一個value

最簡單的方法是使用單個acceptor代理來選中一個值。某個proposer發送一個提議給acceptor,acceptor選擇它接收到的第一條消息的value。雖然簡單,但是這種方法不能滿足我們的要求,因爲假如acceptor發生故障,就會導致接下來的步驟失敗。

因此,讓我們嘗試其它的方式來選擇一個value。現在用多個acceptor代理來代替單個acceptor。某個proposer給這些acceptor都發送一個提議value。某個acceptor可能會接受這個value。那麼這個value當足夠數量的acceptor都接受這個value時,就能通過選中這次value了。多大的數量才足夠?爲了保證僅僅只有一個value被選中,我們讓超過半數的acceptor作爲這個足夠大的數量。因爲對於一個acceptor集合,其中任意兩個超過半數的子集合至少有一個公共的acceptor。如果一個acceptor只能選中至多一個值,那麼這種方法就是可行的。

在沒有故障發生和消息丟失的情況下,我們想要讓一個value被選中,哪怕僅僅只有單個proposer發起一次提議value。這就需要滿足以下要求:

       P1: An acceptormust accept the first proposal that it receives.

       (一個acceptor必須接受它第一個收到的提議的value)

但是這個要求又引起一個問題。多個values可能同時被好幾個不同proposers提議,導致了一種情形:每個acceptor都接收到value,但是沒有一個value是被超過半數的acceptor所選中。即使只提出了兩個提議value,如果每個value各自都被半數acceptor選中,那麼任意單個acceptor故障都可能讓leaner獲取哪一個value被選中變爲不可能。

P1規約以及一個value只有被超過半數acceptor所接受纔算被選中這兩個條件意味着一個acceptor必須能夠接受多個提議。我們給不同的提議指定一個編號來追蹤這些提議,因此一個提議就由提議號(proposal number)和value組成。爲了防止衝突,我們要求不同的提議用不同的提議號。這個不同的提議號如何實現,依賴具體的實現,當前我們先假設它(已經實現)。如果一個提議的value被超過半數的acceptor所接受,那麼這個value就被選中。這種情況下,我們就說這個提議被選中。

我們可以允許多個提議被選中,但是我們必須保證這些被選中的提議都擁有相同的value。通過歸納提議號,就足以保證:

       P2: If a proposal with value v is chosen,then every higher-numbered pro-posal that is chosen has value v.

       (如果一個value爲v的提議被選中,那麼其後每一個更高提議號的提議value也要是v)

因爲提議號都是有序的,條件P2保證了關鍵的安全性屬性:僅僅只有一個value能被選中。

爲了被選中,一個提議必須被至少一個acceptor所接受。因此,我們能通過滿足以下條件來滿足P2:

P2a: If aproposal with value v is chosen, then every higher-numbered pro-posal accepted by any acceptor has value v.

(如果一個value爲v的提議被選中,那麼每個被acceptor所接受的具有更高提議號的提議的value也要是v)

我們依然需要P1來保證有提議能被選中。因爲交流是異步的,一個提議可能被一些特殊的acceptor c所選中,但是它們沒有接受過其它任何提議。假設一個新的proposer“醒過來(重啓或從故障中恢復)”,然後發送了一個帶有更高提議號且不同value的提議。P1只要求c接受這個提議,但是卻違背了P2a。爲了同時滿足P1和P2a,需要加強P2a:

       P2b: If a proposal with value v ischosen, then every higher-numbered pro-posal issued by any proposer has value v.

       (如果一個value爲v的提議被選中,那麼所有發送具有更高提議號的proposer的value也是v)

因爲一個提議在被acceptor接受之前必須是proposer發出,P2b滿足了P2a,也滿足了P2。

爲了明白如何滿足P2b,讓我們思考如何證明它。我們假設有一些提議號爲m,value爲v的提議已經被選中,然後我們證明以後的任何提議號n(n>m)的提議,其value爲v。我們可以歸納法到n來簡化證明,在這種假設下,我們就證明所有發起的提議號爲m..(n-1)的提議,其value都爲v,其中i..j表示提議號範圍爲i-j。既然提議號爲m的提議被選中,那麼必然有一個超過半數acceptors的集合C接受了它。與歸納假設結合,假設提議m被選中可以推論出:

Everyacceptor in C has accepted a proposal with number in m ..(n − 1),and every proposal with number in m ..(n − 1) accepted by any acceptor has value v.

(超半數集合C中的每一個acceptor都接受了一個提議號從m..(n-1)的提議,且每一個提議被接受時,value都爲v

因爲任意超半數的acceptor集合S中,至少有一個是C的成員,我們可以得出結論,通過確保以下的條件來保持提議n的value爲v:

       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 amajority of acceptors such thateither (a) no acceptor in S has acceptedany proposal numbered less than n, or (b) v is the value of the highest-numbered proposal among all proposals numbered less than n acceptedby the acceptors in S.

       (對於任意的提議n和value v,如果一個提議號爲n,value爲v的提議產生了,那麼就存在一個包含了超過半數acceptor的集合S,這個集合中要麼沒有acceptor批准過任意比n小的提議,要麼v就是S中的acceptor選中的提議號小於n的最大編號提議所具有的value)

我們可以因此滿足P2c來滿足P2b。

爲了滿足P2c,一個proposer想產生一個提議n,它就必須獲取提議號小於n且已經或將要被超過半數acceptor接受的最大提議的value。獲取已經被接受過的提議是很簡單的,但是預測將要被接受的結果就很困難。不用預測未來,proposer只要承諾自己不會獲得這樣的接受情況就可以了。換句話說,proposer請求所有acceptor不要接受任何比(它自己提議號)n小的提議。這就推導出一下算法來生成提議:

1.     一個proposer選擇新的提議號n,然後發送請求給所有acceptor,詢問它回覆如下內容:

(a)   承諾不再接受比n小的提議

(b)   如果接受了小於n的提議,就返回接受的提議內容

我們稱這樣的請求爲帶有提議號爲n的prepare請求

2.     如果proposer接受到超過半數的acceptor響應,接下來它就能發出value爲v的提議n,value v要麼是響應的消息中acceptor接受過的最大提議號的value,要麼是所有acceptor都還沒接受value時proposer自己提議的value。

 一個proposer發送提議消息給一些acceptor集合,請求它們接受。(這個集合不一定就是初始請求後響應了的acceptor集合)讓我們稱它爲accept請求。

這個描述的是proposer的算法。Acceptor的算法呢?acceptor能接受兩種請求:prepare請求和accept請求。一個acceptor能夠在不影響安全性的條件下忽略任何請求。所以,我們只需要討論它可以回覆(proposer)請求的情況。它通常都能夠響應一個prepare請求。如果acceptor沒有承諾不接受某種提議,那麼它就能響應這種提議,並接受它。換句話說:

       P1a:An acceptor can accept a proposal numbered n if it has not responded to a prepare request having a numbergreater than n.

       (一個acceptor如果沒有響應比n大的prepare請求,它就能接受提議n)

可以看到P1a包含P1。

現在我們已經有了一個完整的並能滿足安全性的算法來選中一個value—假設使用的都是唯一的提議編號的情況。最終的算法通過一點點的小優化來實現。

假設一個acceptor接收到一個prepare請求n,但是它早已響應過了比n大的prepare請求,因此它承諾不會接受任何提議n。acceptor也沒有任何理由響應這次prepare請求,因此它不會接受提議n。我們就讓acceptor忽略這種prepare請求。同時我們也讓acceptor忽略早就被接受過的prepare請求。

經過這種優化,acceptor僅僅只需要記住它曾接受過的最大編號的提議和它響應過的最大編號的prepare請求。P2c必須被滿足哪怕發生了故障,因此acceptor也必須記住這些信息即使發生了故障和重啓。注意proposer可以丟棄一個提議然後忘掉所有信息,只要它不會又發送相同編號的提議。

將proposer和acceptor放在一起,我們可以得到算法操作分下面兩階段:

階段1:

(a)   某個proposer選擇一個提議號n,然後用提議號n發送一個prepare請求給超過半數的acceptor;

(b)   如果一個acceptor接收到提議號爲n的prepare請求,且提議號n比它曾經響應過的提議號更大,那麼它就響應proposer,響應內容是:保證不會接受任何比n更小的提議以及它曾經接受過的最大編號提議的value(如果有的話)

階段2:

(a)   如果proposer接收到超過半數的acceptor對於提議n的響應,那麼它就給每個響應了的acceptor發送一個accept請求,提議號還是n,且value要麼是那些acceptor響應消息裏帶有接受過的最大提議號的value,要麼那些acceptor都沒有接受任何提議,那value就是proposer自己任選;

(b)   如果一個acceptor接收到提議號爲n的accept請求,它就會接受這次accept請求(並選中請求帶有的value)除非它之前又響應過比n更高的prepare請求。

一個proposer可以產生多個提議,只要它能滿足算法的每個要求。它也可以在協議進行到任何階段任何時間丟棄提議。(正確性是能保證的,哪怕某個提議請求或者其響應消息可能在很久之後纔到達目的地,而這之前這個提議就被丟棄了。)當有其它proposer已經開始用更高的提議號嘗試發起投票了,放棄此次提議是個好主意。因此,如果一個acceptor因爲它已經接受過更高提議號的提議而忽略當前prepare或accept請求時,它應該通知那個proposer讓它放棄這次提議。這是一個並不影響正確性的優化點。

2.3 獲取一個被選中的值

(learner)爲了習得一個被選中的value,一個learner必須找出被大多數批准者接受的那次提議。明顯的算法是讓每個acceptor無論什麼時候選中了一個提議,就給所有learner響應那次提議的信息。這可以讓learners儘快知道選中的value,但是這種方法需要每個acceptor給每個learner發送大量的消息。

非拜占庭模型(non-Byzantine model)錯誤的假設下,某個learner能夠容易地從其它learner那裏知道被選中的value。我們可以讓acceptor響應批准的投票信息給distinguished learner(主學習者),然後distinguished learner再輪流響應給其它learner。這種方法需要額外一輪操作來讓所有learner發現被選中的value。它也是不可靠的,因爲distinguished learner可能會發生異常。並且它也需要acceptor的數量加上learner的數量這麼多消息響應。

更普遍的做法是,acceptors可以響應它們的批准信息給部分被選中distinguishedlearners,每一個distinguished learner又能通知所有learner。如果distinguished learners的數量很多,這可以保證更好地可靠性,但也帶來了交流複雜性上的消耗。

由於分佈式中消息可能出現丟失,就可能出現一個提議被選中了但是沒有learner知道這次投票結果。learners可以詢問acceptors哪一個提案被選中了,假設出現了一個acceptor不知道是哪次提案被選中了或者沒有提案出現了超過半數的投票。在這種情況下,只有新的一次提案被選中了,learners才能知道哪一個value被選中。如果一個learner需要知道一個value是否被選中,它可以讓一個proposer發起一次這個value的提議。

2.4 過程保證

我們可以簡單構建一種情形:兩個proposer不斷地用更高的序號發起投票,但是沒有一次投票被選中(活鎖)。proposer p用提議號n1完成了階段1(Phase 1)。接着另一個proposer q又用大於n1的提議號n2完成了階段1(Phase1)。接着p開始進行階段2(Phase 2)(因爲階段1用n1收到的超過半數響應因此可以進行階段2),但是發送的accept請求沒有收到響應或者收到的都是拒絕消息,因爲n1編號小於此時acceptors維護的編號n2。因此,p又用新的編號n3發起投票,並完成階段1。再來看proposer q,它當前完成了階段1,然後發起第二階段消息請求,但是收到了拒絕的響應(因爲n2<n3了),它又用n4發起新投票……如此往復。

爲了保證過程(正常進行),必須要選一個distinguished proposer來發起提案。如果這個distinguished proposer能成功地跟超過半數acceptors交流,並且每次都使用比曾經使用的提議編號大的編號,那麼它就能成功發起一次提議並被acceptors接受。當distinguished proposer知道自己(發起提議)的編號太低時,通過放棄提議並且重新(用更高的編號)嘗試,最終選擇一個足夠高的編號。

2.5 實現

Paxos算法假定一個有多個進程的網絡。在它的共識算法中,每個進程同時扮演proposer、acceptor和learner。通過算法選擇一個leader來作爲distinguished proposer和distinguished learner。Paxos一致性算法正是上面描述的消息和請求都作爲普通消息發送的確切實現算法。(響應消息也一樣被標記上一致的提議編號來防止混淆。)當異常發生時,穩定的存儲用來持久化批准者必須記住的信息。響應proposer之前,acceptor在存儲系統中記錄它準備要響應的消息。

最後剩下要做的就是描述如何避免兩個提議用了兩個相同的提議編號。如果不同的proposer從不相交的數字集合裏選擇他們的提議編號,那麼兩個提議絕不會產生相同編號的提議。每個proposer持久化它用過的最高的提案編號,然後在開始Paxos一階段時就使用一個比用過的最高的提案編號更高的編號。

3 實現一個狀態機

實現一個分佈式系統的簡單方式是(將分佈式系統的節點)當做一批客戶端,都產生命令到一箇中央服務器。這個中央服務器能被描述爲按某種順序來執行客戶端命令的一個確定性狀態機。這個狀態機有一個當前狀態;它執行一步可以看作:輸入一個命令->產生一個輸出結果->切換到新狀態。舉個例,分佈式銀行系統的客戶端可能是出納員,系統裏的狀態機維護的狀態信息可能包含所有用戶的賬戶餘額。那麼一次提款就能描述爲:僅當餘額比提款額大時,執行“減少賬戶餘額”的狀態機命令->產生一個包含舊餘額和新餘額的結果。

僅使用單箇中央服務器的實現會面臨單點失效問題。因此用一批服務器來代替一箇中央服務器,每個服務器都是一個獨立的狀態機。因爲這些狀態機是確定性的,如果它們接收並執行的指令序列都是相同的話,那麼它們也會產生相同狀態和輸出結果。一個客戶端產生一個狀態機命令時就能使用任意一箇中央服務器的結果。

爲了保證所有的中央服務器執行相同序列的狀態機命令,我們實現了由Paxos共識算法的單獨實例組成的序列:序列中第i個投票的值就作爲第i個狀態機命令。每個服務器都扮演所有角色(proposer、acceptor、learner)。現在,我假定一組中央服務器已經確定,因此所有一致性算法的實例都使用一樣的集合。

正常操作時,在所有這個一致性算法實例中一個節點被選爲leader(唯一的一個能產生提議的節點)。客戶端發送多條命令給這個leader,leader來決定這些命令該放在實例集合中的位置。如果leader從這衆多命令中決定了一個確切的客戶端命令應該作爲第135條命令,它就會嘗試將這條命令作爲這個共識算法的第135個實例。這個嘗試通常會成功。以下情況可能會失敗:leader出現異常情況、或者有一個其它節點服務器也堅信自己是leader並且對於哪條客戶端命令作爲第135條命令有不同看法。但是這個共識算法保證至多一個命令能被選擇爲第135條命令。

這個方法效率的關鍵是:在這個Paxos共識算法中,value只能到第二階段(Phase 2)才能被批准。

回憶一下,proposer的算法在完成階段1之後,要麼proposer已經知道acceptor選中了一個value,要麼所有acceptors還沒有經歷過階段2並同意過任何值。

我現在要描述Paxos算法提到的狀態機在正常操作中如何實現。稍後,我會討論異常情況。我思考當前一個leader崩潰了而新leader被選出時會發生什麼。(系統初始運行時還沒有命令被投票產生,這是一種特殊情形。)

選出的新的leader也作爲這個共識算法所有實例的learner,應該要知道大多數被選擇的命令。假設它知道第1-134、138和139個命令,也就是這些實例1-134、138和139選擇的value。(稍後我們會看到這種情況如何產生的。)leader接着開始執行第135-137實例的階段1(Phase 1)以及所有比139大的實例。假設這次的執行結果決定了實例135和140選中的value,但是不影響其它實例。然後leader對實例135和140執行階段2(Phase 2),從而爲135和140實例選中的客戶端命令。

Leader服務器和其它所有服務器此時可以獲取leader能夠知道的所有命令,那麼現在它們可以執行1-135實例的命令。然而,它們不能執行138-140,儘管138-140處於實例集合中能夠知道它們的內容,因爲命令136和137還沒有選出。Leader可以將接下來的兩條客戶端命令請求作爲命令136和137。但是我們不這樣做,我們發起一個特殊的”no-op”命令的提議來作爲136和137的內容,這樣讓狀態機狀態不改變並且也能填充命令實例集合的空缺。(通過執行共識算法實例136和137的階段2)一旦這些”no-op”命令被選中,命令138-140就能被執行。

  現在命令1-140已經選擇完畢了。Leader也完成了共識算法中所有大於140的實例的階段1,並且可以在這些實例的階段2提議任何value。它將接下來的客戶端請求賦值爲141,然後提議這個客戶端請求作爲共識算法實例141在階段2的value。它又繼續提議接下來的客戶端請求爲142,如此循環往復。

  Leader能夠在還不清楚提議141是否被選中之前就提議命令142。可能提議141命令的所有消息都丟失了,也可能在命令141還沒被任何learner知道被選爲141命令之前,142命令就被選中。當leader沒有接收到期望的關於141實例提議的階段2響應消息,它就會重新發送這些消息。如果所有順利進行,它的提議命令將會被選中。然而,它可能一開始就發生故障,使選擇的命令序列出現了空缺。通常,假設一個leader能夠提前獲得α條命令,那也是說,在1-i的命令被選出的情況下,它能提議i+1-i+α的命令。因此空缺可能會有α-1那麼大。

  一個新的leader可以執行無數個共識算法實例的階段1—即在上面的場景中,135-137以及所有大於139的實例。所有實例使用同一個提議號,leader可以給其它服務器發送單個合理的短消息。在階段1,一個acceptor如果早已接收過其它proposer的階段2的消息,那麼(對於這條prepare請求)它就不僅僅回覆個OK。(這種情況僅僅是場景中的135和140實例。)因此,一個服務器(扮演acceptor的)可以用單個合理的短消息響應所有實例。執行無數個階段1的實例不會產生任何問題。

  leader遭遇故障然後選擇一個新的leader是小概率事件,因此執行狀態機命令的消耗--即,實現共識算法的command/value—就是共識算法階段2執行的消耗。可以看到Paxos一致性算法的階段2在任何達成共識的算法中消耗最小的。因此,Paxos算法是最優的。

  系統正常操作中總是假定有一個leader,除非當前leader發生故障並且正在選擇一個新leader。在意外情況中,leader選舉可能發生故障。如果沒有服務器扮演leader,那麼也就沒有命令會被提議。如果有多個服務器認爲他們是leaders,它們都能對一致性算法的同一實例的提議value,這會導致沒有任何value被選中。然而安全性總是保留着—兩個不同的服務器永遠不會不同意一個value被選爲第i個狀態機命令。單個leader的選舉僅僅是爲了保證過程的進行。

  如果服務器的集合可以改變,那麼必須要有方法來確定哪些服務器實現了哪些一致性算法實例。最簡單的辦法就是讓狀態機自己來做。當前服務器的集合可以作爲狀態的一部分,並且可以被一般的狀態機命令改變。在執行完第i條狀態機命令後,我們通過讓這個服務器集合執行一致性算法的i-i+α條實例來指定狀態的方式,以允許leader提前獲取α個命令。這點就可以完成一個任意複雜的重構算法的簡單實現。

 

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章