Paxos共識演算法詳解

雜湊表演藝術家發表於2019-04-12

在一個分散式系統中,由於節點故障、網路延遲等各種原因,根據CAP理論,我們只能保證一致性(Consistency)、可用性(Availability)、分割槽容錯性(Partition Tolerance) 中的兩個。

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

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

圖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的簡化和改進,變得更加容易理解和實現。

Paxos型別

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

圖2

圖2

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

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

Basic Paxos

角色

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

Paxos共識演算法詳解

圖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所示:

Paxos共識演算法詳解

圖4

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

三種情況

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

情況1:提案已接受

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

序列號.Server ID

例如S1提出的提案編號,就是1.1、2.1、3.1……

Paxos共識演算法詳解

圖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可見

Paxos共識演算法詳解

圖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不可見

Paxos共識演算法詳解

圖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階段發生互相搶佔的情形。

Paxos共識演算法詳解

圖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。

Paxos共識演算法詳解

圖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直接標記的話可能會不一致。

Paxos共識演算法詳解

圖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許可協議,轉載時請註明連結。

相關文章