Paxos共識算法詳解

在一個分佈式系統中,因爲節點故障、網絡延遲等各類緣由,根據CAP理論,咱們只能保證一致性(Consistency)、可用性(Availability)、分區容錯性(Partition Tolerance) 中的兩個。html

對於一致性要求高的系統,好比銀行取款機,就會選擇犧牲可用性,故障時拒絕服務。MongoDB、Redis、MapReduce使用這種方案。git

對於靜態網站、實時性較弱的查詢類數據庫,會犧牲一致性,容許一段時間內不一致。簡單分佈式協議Gossip,數據庫CouchDB、Cassandra使用這種方案。web

圖1

圖1算法

如圖1所示,一致性問題,能夠根據是否存在惡意節點分類兩類。無惡意節點,是指節點會丟失、重發、不響應消息,但不會篡改消息。而惡意節點可能會篡改消息。有惡意節點的問題稱爲拜占庭將軍問題,不在今天的討論範圍。Paxos很好地解決了無惡意節點的分佈式一致性問題。數據庫

背景

1990年,Leslie Lamport在論文《The Part-Time Parliament》中提出Paxos算法。因爲論文使用故事的方式,沒有使用數學證實,起初並無獲得重視。直到1998年該論文才被正式接受。後來2001年Lamport又從新組織了論文,發表了《Paxos Made Simple》。做爲分佈式系統領域的早期貢獻者,Lamport得到了2013年圖靈獎。網絡

Paxos算法普遍應用在分佈式系統中,Google Chubby的做者Mike Burrows說:「這個世界上只有一種一致性算法,那就是 Paxos(There is only one consensus protocol, and that's Paxos)」。分佈式

後來的Raft算法、是對Paxos的簡化和改進,變得更加容易理解和實現。ide

Paxos類型

Paxos原本是虛構故事中的一個小島,議會經過表決來達成共識。可是議員可能離開,信使可能走丟,或者重複傳遞消息。對應到分佈式系統的節點故障和網絡故障。性能

圖2

圖2優化

如圖2所示,假設議員要提議中午吃什麼。若是有一個或者多我的同時提議,但一次只能經過一個提議,這就是Basic Paxos,是Paxos中最基礎的協議。

顯然Basic Paxos是不夠高效的,若是將Basic Paxos並行起來,同時提出多個提議,好比中午吃什麼、吃完去哪裏嗨皮、誰請客等提議,議員也能夠同時經過多個提議。這就是Multi-Paxos協議。

Basic Paxos

角色

Paxos算法存在3種角色:Proposer、Acceptor、Learner,在實現中一個節點能夠擔任多個角色。

圖3

  • Proposer負責提出提案
  • Acceptor負責對提案進行投票
  • Learner獲取投票結果,並幫忙傳播

Learner不參與投票過程,爲了簡化描述,咱們直接忽略掉這個角色。

算法

運行過程分爲兩個階段,Prepare階段和Accept階段。

Proposer須要發出兩次請求,Prepare請求和Accept請求。Acceptor根據其收集的信息,接受或者拒絕提案。

Prepare階段

  • Proposer選擇一個提案編號n,發送Prepare(n)請求給超過半數(或更多)的Acceptor。
  • Acceptor收到消息後,若是n比它以前見過的編號大,就回復這個消息,並且之後不會接受小於n的提案。另外,若是以前已經接受了小於n的提案,回覆那個提案編號和內容給Proposer。

Accept階段

  • 當Proposer收到超過半數的回覆時,就能夠發送Accept(n, value)請求了。 n就是本身的提案編號,value是Acceptor回覆的最大提案編號對應的value,若是Acceptor沒有回覆任何提案,value就是Proposer本身的提案內容。
  • Acceptor收到消息後,若是n大於等於以前見過的最大編號,就記錄這個提案編號和內容,回覆請求表示接受。
  • 當Proposer收到超過半數的回覆時,說明本身的提案已經被接受。不然回到第一步從新發起提案。

完整算法如圖4所示:

圖4

Acceptor須要持久化存儲minProposal、acceptedProposal、acceptedValue這3個值。

三種狀況

Basic Paxos共識過程一共有三種可能的狀況。下面分別進行介紹。

狀況1:提案已接受

如圖5所示。X、Y表明客戶端,S1到S5是服務端,既表明Proposer又表明Acceptor。爲了防止重複,Proposer提出的編號由兩部分組成:

序列號.Server ID

例如S1提出的提案編號,就是1.一、2.一、3.1……

圖5 以上圖片來自Paxos lecture (Raft user study)第13頁

這個過程表示,S1收到客戶端的提案X,因而S1做爲Proposer,給S1-S3發送Prepare(3.1)請求,因爲Acceptor S1-S3沒有接受過任何提案,因此接受該提案。而後Proposer S1-S3發送Accept(3.1, X)請求,提案X成功被接受。

在提案X被接受後,S5收到客戶端的提案Y,S5給S3-S5發送Prepare(4.5)請求。對S3來講,4.5比3.1大,且已經接受了X,它會回覆這個提案 (3.1, X)。S5收到S3-S5的回覆後,使用X替換本身的Y,因而發送Accept(4.5, X)請求。S3-S5接受提案。最終全部Acceptor達成一致,都擁有相同的值X。

這種狀況的結果是:新Proposer會使用已接受的提案

狀況2:提案未接受,新Proposer可見

圖6 以上圖片來自Paxos lecture (Raft user study)第14頁

如圖6所示,S3接受了提案(3.1, X),但S1-S2尚未收到請求。此時S3-S5收到Prepare(4.5),S3會回覆已經接受的提案(3.1, X),S5將提案值Y替換成X,發送Accept(4.5, X)給S3-S5,對S3來講,編號4.5大於3.1,因此會接受這個提案。

而後S1-S2接受Accept(3.1, X),最終全部Acceptor達成一致。

這種狀況的結果是:新Proposer會使用已提交的值,兩個提案都能成功

狀況3:提案未接受,新Proposer不可見

圖7 以上圖片來自Paxos lecture (Raft user study)第15頁

如圖7所示,S1接受了提案(3.1, X),S3先收到Prepare(4.5),後收到Accept(3.1, X),因爲3.1小於4.5,會直接拒絕這個提案。因此提案X沒法收到超過半數的回覆,這個提案就被阻止了。提案Y能夠順利經過。

這種狀況的結果是:新Proposer使用本身的提案,舊提案被阻止

活鎖 (livelock)

活鎖發生的概率很小,可是會嚴重影響性能。就是兩個或者多個Proposer在Prepare階段發生互相搶佔的情形。

圖8 以上圖片來自Paxos lecture (Raft user study)第16頁

解決方案是Proposer失敗以後給一個隨機的等待時間,這樣就減小同時請求的可能。

Multi-Paxos

上一小節提到的活鎖,也可使用Multi-Paxos來解決。它會從Proposer中選出一個Leader,只由Leader提交Proposal,還能夠省去Prepare階段,減小了性能損失。固然,直接把Basic Paxos的多個Proposer的機制搬過來也是能夠的,只是性能不夠高。

將Basic Paxos並行以後,就能夠同時處理多個提案了,所以要能存儲不一樣的提案,也要保證提案的順序。

Acceptor的結構如圖9所示,每一個方塊表明一個Entry,用於存儲提案值。用遞增的Index來區分Entry。

圖9

Multi-Paxos須要解決幾個問題,咱們逐個來看。

1. Leader選舉

一個最簡單的選舉方法,就是Server ID最大的當Leader。

每一個Server間隔T時間向其餘Server發送心跳包,若是一個Server在2T時間內沒有收到來自更高ID的心跳,那麼它就成爲Leader。

其餘Proposer,必須拒絕客戶端的請求,或將請求轉發給Leader。

固然,還可使用其餘更復雜的選舉方法,這裏再也不詳述。

2. 省略Prepare階段

Prepare的做用是阻止舊的提案,以及檢查是否有已接受的提案值。

當只有一個Leader發送提案的時候,Prepare是不會產生衝突的,能夠省略Prepare階段,這樣就能夠減小一半RPC請求。

Prepare請求的邏輯修改成:

  • Acceptor記錄一個全局的最大提案編號
  • 回覆最大提案編號,若是當前entry以及以後的全部entry都沒有接受任何提案,回覆noMoreAccepted

當Leader收到超過半數的noMoreAccepted回覆,以後就不須要Prepare階段了,只須要發送Accept請求。直到Accept被拒絕,就從新須要Prepare階段。

3. 完整信息流

目前爲止信息是不完整的。

  • Basic Paxos只需超過半數的節點達成一致。可是在Multi-Paxos中,這種方式可能會使一些節點沒法獲得完整的entry信息。咱們但願每一個節點都擁有所有的信息。
  • 只有Proposer知道一個提案是否被接受了(根據收到的回覆),而Acceptor沒法得知此信息。

第1個問題的解決方案很簡單,就是Proposer給所有節點發送Accept請求。

第2個問題稍微複雜一些。首先,咱們能夠增長一個Success RPC,讓Proposer顯式地告訴Acceptor,哪一個提案已經被接受了,這個是徹底可行的,只不過還能夠優化一下,減小請求次數。

咱們在Accept請求中,增長一個firstUnchosenIndex參數,表示Proposer的第一個未接受的Index,這個參數隱含的意思是,對該Proposer來講,小於Index的提案都已經被接受了。所以Acceptor能夠利用這個信息,把小於Index的提案標記爲已接受。另外要注意的是,只能標記該Proposer的提案,由於若是發生Leader切換,不一樣的Proposer擁有的信息可能不一樣,不區分Proposer直接標記的話可能會不一致。

圖10

如圖10所示,Proposer正在準備提交Index=2的Accept請求,0和1是已接受的提案,所以firstUnchosenIndex=2。當Acceptor收到請求後,比較Index,就能夠將Dumplings提案標記爲已接受。

因爲以前提到的Leader切換的狀況,仍然須要顯式請求才能得到完整信息。在Acceptor回覆Accept消息時,帶上本身的firstUnchosenIndex。若是比Proposer的小,那麼就須要發送Success(index, value),Acceptor將收到的index標記爲已接受,再回復新的firstUnchosenIndex,如此往復直到二者的index相等。

總結

Paxos是分佈式一致性問題中的重要共識算法。這篇文章分別介紹了最基礎的Basic Paxos,和可以並行的Multi-Paxos。

在Basic Paxos中,介紹了3種基本角色Proposer、Acceptor、Learner,以及提案時可能發生的3種基本狀況。在Multi-Paxos中,介紹了3個須要解決的問題:Leader選舉、Prepare省略、完整信息流。

在下一篇文章中,咱們將實現一個簡單的demo來驗證這個算法,實現過程將會涉及到更多的細節。

Reference

分佈式一致性與共識算法

Paxos 算法與 Raft 算法

Paxos

Paxos Made Simple

Paxos lecture (Raft user study)

YouTube | Paxos lecture (Raft user study)

版權

本做品採用CC BY 4.0許可協議,轉載時請註明連接。

相關文章
相關標籤/搜索