Paxos——分散式一致性演算法

王老魔發表於2019-05-03

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分散式一致性原理與實踐》

提案的提出和批准
  • 階段一

    1. Proposer選擇一個提案編號N,然後向半數以上的Acceptor傳送編號為N的Prepare請求。
    2. 如果一個Acceptor收到一個編號為N的Prepare請求,且N大於該Acceptor已經響應過的所有Prepare請求的編號,那麼它就會將它已經接受過的編號最大的提案(如果有的話)作為響應反饋給Proposer,同時該Acceptor承諾不再接受任何編號小於N的提案。
  • 階段二

    1. 如果Proposer收到半數以上Acceptor對其發出的編號為N的Prepare請求的響應,那麼它就會傳送一個針對[N,V]提案的Accept請求給半數以上的Acceptor。注意:V就是收到的響應中編號最大的提案的value,如果響應中不包含任何提案,那麼V就由Proposer自己決定。

    2. 如果Acceptor收到一個針對編號為N的提案的Accept請求,只要該Acceptor沒有對編號大於N的Prepare請求做出過響應,它就接受該提案。

提案的釋出
  1. acceptors需要將accept訊息傳送給learners的一個子集,然後由這些learners去通知所有learners。
  2. 但是由於訊息傳遞的不確定性,可能會沒有任何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。

他們之間的關係可以用下圖來說明

圖二.png

當Acceptor僅可批准一個提案時,僅依靠P1,也是能夠只批准出一個Value的,但是在這種情況下,很有可能需要多次重複投票過程才能夠達到一致性的狀態,也就是說雖然能夠保證安全性,但是犧牲了部分的活性。如下圖所示:

圖三.png

Proposer總是能夠優先獲得同機房內的Acceptor的批准,但是很難獲得其他機房的Acceptor的批准,這時ProposerA、ProposerB、ProposerC各獲得一票,每個Proposer的提案都沒有通過,此時Proposer只能生成編號更大的提案,以期許能夠獲得大多數的Acceptor(2個)的批准,也許未來不久,某個lucky dog最終能夠獲得大多數的Acceptor的批准,但是我們已經等的花兒都謝了。

所以為了能夠快速到達一致性,又引入了P2c和P1a,在P1a中解除了Acceptor只能批准一個提案的限制,但是增加了對於批准提案的編號的限制,在P2中增加了對Proposer提出提案的Value值的限制,這兩個限制帶來的直接效果有兩個:

  1. 原本Proposer只需要和Acceptor互動一次,現在變成了兩次,在Proposer正式提交提案前,Proposer要先獲得大多數的Acceptor的狀態(prepare請求),以確保提出的提案時,沒有已經通過了的提案。因為是大多數的Acceptor,所以如果有已審批的提案,那麼一定能夠通過這批Prepare請求獲知,如果得知已經有審批過的提案,那麼代表Proposer已獲知本次Paxos執行例項中的決議,並將自己的提案的Value值替換為已審批過的提案的Value值,保證安全性。
  2. 因為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”的提案。

圖四.png

如圖四所示,最終形成了值為A的提案。

圖五.png

如圖五所示,最終形成了值為B的提案。

這時候停下來思考一下,嚴格來說,上面描述的犧牲活性問題並沒有解決,只是降低了發生了的概率,在極端情況下還是能夠發生一種類似於“活鎖”的狀態的。如下圖所示

圖六

在極端情況下,這種迴圈會一直進行下去。所以為了解決這種問題,又提出了Multi-Paxos演算法,Multi-Paxos具體演算法在這裡不做陳述,它是在Proposer中又搞了一個Leader的概念,在初期,所有的Proposer中競選出一個Leader,然後只有Leader能夠向Acceptor提出提案,當Leader發生問題時,再競選一個Leader出來,沒有了Proposer的競爭,兩階段也變成了一階段,提高了效率,也解決了活鎖的問題。但是仔細想想,競選Leader的過程中也可能會發生活鎖的,我估計這也是Raft演算法被提出來的真正原因(狗頭),畢竟最後繞了一大圈,最終還是搞出了單點的Leader出來進行管理,還不如用上面P1+重試的機制選出Leader,效率平時是差不多的,僅在選舉Leader時會比較慢而已。

總結


本文分析了Paxos演算法的應用價值和具體實現原理,希望能讓大家在學習Paxos演算法的過程中能夠少掉一點頭髮。後續可能還會更新我對Raft演算法的理解。


歡迎關注我的部落格:王老魔的程式碼備忘錄

相關文章