Google Chubby的作者Mike Burrows說過這個世界上只有一種一致性演算法,那就是Paxos,其它的演算法都是殘次品。
Paxos演算法問世已經有將近30年的歷史了,是目前公認最有效的解決分散式場景下一致性問題的演算法之一,但是缺點是比較難懂,工程化比較難。本文希望能夠輔以圖例和通俗易懂的例項把Paxos演算法講清楚。
Paxos演算法的價值
在分散式系統中,在非同步通訊的過程中,總會發生網路波動、機器當機等情況,那麼如何在這樣複雜的情況下,快速且安全的就某一數值達成一致呢?Paxos演算法主要就是解決此類問題,在布式鎖、主從複製、命名服務、分散式協調等常見場景下,Paxos演算法都有著廣泛的應用。
基本概念
參與角色
在Paxos演算法中,所有的參與者被分為了以下幾個角色
角色 | 分工 | 參與決策 |
---|---|---|
Proposer | 提出提案,提案:[編號Id,提議的Value] | √ |
Acceptor | 接收提案,批准/拒絕提案,當提案被大多數的Acceptor(Quorum)批准後即為被選定的提案(Chosen) | √ |
Learner | 學習(Learn)最新被選定的提案 | × |
- 提案:提案是由編號及Value組成,Paxos演算法需要我們保證提案的編號Id全域性唯一有序(具體有很多種實現,不在本文的討論範圍內)。
- Quorum:直譯為法定人數,在Paxos中意為任意兩個Quorum都包含至少一個公共成員,可以理解為包含Acceptor集合中的大多數成員。如一共有2F+1位Acceptor,則Quorum人數為F+1位。
- Proposer、Acceptor、Learner只是角色的分工,在具體實現中,一個程式可能擔當不止一種角色。
Paxos演算法正確的必要條件
現在將演算法的參與者分為了這樣三個角色,那麼是為了讓他們完成什麼樣的工作目標呢?
一個分散式演算法有兩個最重要的屬性:活性、安全性
- 活性意為“預期的事情最終一定會發生”,最終一致性就是一種活性。
- 安全性意為違背了安全性規則,則系統會發生損失。
我們可以從這兩個方面來考察Paxos演算法的正確性
活性:
保證最終有一個提案會被選定,當提案被選定後,程式最終最終也能獲取到被選定的提案。
安全性:
- 提案(value)只有在被 proposers 提出後才能被批准。
- 在一次 Paxos 演算法的執行例項中,只批准(chosen)一個 value。
- learners 只能獲得被批准(chosen)的 value。
那麼我們下面來看看具體的演算法流程
演算法流程
演算法描述來自於倪超《從Paxos到ZooKeeper分散式一致性原理與實踐》
提案的提出和批准
-
階段一
- Proposer選擇一個提案編號N,然後向半數以上的Acceptor傳送編號為N的Prepare請求。
- 如果一個Acceptor收到一個編號為N的Prepare請求,且N大於該Acceptor已經響應過的所有Prepare請求的編號,那麼它就會將它已經接受過的編號最大的提案(如果有的話)作為響應反饋給Proposer,同時該Acceptor承諾不再接受任何編號小於N的提案。
-
階段二
-
如果Proposer收到半數以上Acceptor對其發出的編號為N的Prepare請求的響應,那麼它就會傳送一個針對[N,V]提案的Accept請求給半數以上的Acceptor。注意:V就是收到的響應中編號最大的提案的value,如果響應中不包含任何提案,那麼V就由Proposer自己決定。
-
如果Acceptor收到一個針對編號為N的提案的Accept請求,只要該Acceptor沒有對編號大於N的Prepare請求做出過響應,它就接受該提案。
-
提案的釋出
- acceptors需要將accept訊息傳送給learners的一個子集,然後由這些learners去通知所有learners。
- 但是由於訊息傳遞的不確定性,可能會沒有任何learner獲得了決議批准的訊息。當learners需要了解決議通過情況時,可以讓一個proposer重新進行一次提案。注意一個learner可能兼任proposer。
乾巴巴的演算法描述可能比較難以理解,所以從圖解分散式一致性協議Paxos這裡借來一個很簡明的圖來輔助理解。
從上圖看到,作為Acceptor只需要儲存批准/保證過的提案的最大編號(MaxN),批准過的提案的最大編號(AcceptN),批准過的提案的Value值(AcceptV),然後通過階段一(2)和階段二(2)的兩種規則進行對提案的審批,即能夠保證審批的安全性。
而Proposer需要保證在階段一(1)時提出的提案編號唯一且單調遞增,而在階段二(1)時只對獲取到了足夠多的保證(即獲得了大多數Acceptor對Proposer的保證)的提案進行提交,即能夠保證提案申請的安全性。
那麼為什麼這樣能夠滿足上面所述的分散式演算法的安全性呢?這個要從Paxos演算法的推導來看。完整的推導過程可以在wikipedia上看到。
下面我來談一談我的理解,在推導過程中有這麼幾個重要的約束:
P1:一個 acceptor 必須接受(accept)第一次收到的提案。
P1a:當且僅當acceptor沒有回應過編號大於n的prepare請求時,acceptor接受(accept)編號為n的提案。
P2:一旦一個具有 value v 的提案被批准(chosen),那麼之後批准(chosen)的提案必須具有 value v。
P2a:一旦一個具有 value v 的提案被批准(chosen),那麼之後任何 acceptor 再次接受(accept)的提案必須具有 value v。
P2b:一旦一個具有 value v 的提案被批准(chosen),那麼以後任何 proposer 提出的提案必須具有 value v。
P2c:如果一個編號為 n 的提案具有 value v,那麼存在一個多數派,要麼他們中所有人都沒有接受(accept)編號小於 n 的任何提案,要麼他們已經接受(accept)的所有編號小於 n 的提案中編號最大的那個提案具有 value v。
他們之間的關係可以用下圖來說明
當Acceptor僅可批准一個提案時,僅依靠P1,也是能夠只批准出一個Value的,但是在這種情況下,很有可能需要多次重複投票過程才能夠達到一致性的狀態,也就是說雖然能夠保證安全性,但是犧牲了部分的活性。如下圖所示:
Proposer總是能夠優先獲得同機房內的Acceptor的批准,但是很難獲得其他機房的Acceptor的批准,這時ProposerA、ProposerB、ProposerC各獲得一票,每個Proposer的提案都沒有通過,此時Proposer只能生成編號更大的提案,以期許能夠獲得大多數的Acceptor(2個)的批准,也許未來不久,某個lucky dog最終能夠獲得大多數的Acceptor的批准,但是我們已經等的花兒都謝了。
所以為了能夠快速到達一致性,又引入了P2c和P1a,在P1a中解除了Acceptor只能批准一個提案的限制,但是增加了對於批准提案的編號的限制,在P2中增加了對Proposer提出提案的Value值的限制,這兩個限制帶來的直接效果有兩個:
- 原本Proposer只需要和Acceptor互動一次,現在變成了兩次,在Proposer正式提交提案前,Proposer要先獲得大多數的Acceptor的狀態(prepare請求),以確保提出的提案時,沒有已經通過了的提案。因為是大多數的Acceptor,所以如果有已審批的提案,那麼一定能夠通過這批Prepare請求獲知,如果得知已經有審批過的提案,那麼代表Proposer已獲知本次Paxos執行例項中的決議,並將自己的提案的Value值替換為已審批過的提案的Value值,保證安全性。
- 因為Proposer在正式提交提案前,已經經過了“嚴格”的問詢和保證,Acceptor也會對審批的編號做稽核,所以即使Acceptor能夠批准多個提案,但是會保證審批通過的提案的值都具有相同的Value值。從而保證了安全性。
這樣講可能還是比較難以理解,我們現在就上面那個例子做一個圖示,分別看看選出提案為A、和提案為B的流程。
- P代表Acceptor對Proposer的Promise
- A代表Acceptor對Proposer提案的Accept
- PE代表保證失敗,即圖一中的
- AE代表審批失敗,即圖一中的
- 提案編號由時間戳和機器Id組成,如編號為1.2,則代表在時間戳為1時,機器Id2提出的提案。
- 字母右邊的數字代表提案編號,如P1.1代表Acceptor對於編號為1.1提案的Promise
- 中括號[]內為回應內容,如P1.1[1.2:A]代表Acceptor對於編號1.1提案的Promise,並回應“我已經審批通過了編號為1.2,值為A”的提案。
如圖四所示,最終形成了值為A的提案。
如圖五所示,最終形成了值為B的提案。
這時候停下來思考一下,嚴格來說,上面描述的犧牲活性問題並沒有解決,只是降低了發生了的概率,在極端情況下還是能夠發生一種類似於“活鎖”的狀態的。如下圖所示
在極端情況下,這種迴圈會一直進行下去。所以為了解決這種問題,又提出了Multi-Paxos演算法,Multi-Paxos具體演算法在這裡不做陳述,它是在Proposer中又搞了一個Leader的概念,在初期,所有的Proposer中競選出一個Leader,然後只有Leader能夠向Acceptor提出提案,當Leader發生問題時,再競選一個Leader出來,沒有了Proposer的競爭,兩階段也變成了一階段,提高了效率,也解決了活鎖的問題。但是仔細想想,競選Leader的過程中也可能會發生活鎖的,我估計這也是Raft演算法被提出來的真正原因(狗頭),畢竟最後繞了一大圈,最終還是搞出了單點的Leader出來進行管理,還不如用上面P1+重試的機制選出Leader,效率平時是差不多的,僅在選舉Leader時會比較慢而已。
總結
本文分析了Paxos演算法的應用價值和具體實現原理,希望能讓大家在學習Paxos演算法的過程中能夠少掉一點頭髮。後續可能還會更新我對Raft演算法的理解。
歡迎關注我的部落格:王老魔的程式碼備忘錄