Paxos 實現日誌複製同步(Basic Paxos)

Paxos 實現日誌複製同步

本篇文章以 John Ousterhout(斯坦福大學教授)Diego Ongaro(斯坦福大學得到博士學位,Raft算法發明人) 在 Youtube 上的講解視頻及 ppt 爲藍本,深刻分析 Paxos 的內部機制,並以日誌複製同步(Replicated Logs)爲背景,詳細介紹使用 Paxos 協議實現日誌複製同步。php

用 Paxos 實現日誌複製同步

image

Paxos 是在十九世紀80年代末由 Leslie Lamport 發明的,從那開始 Paxos 幾乎就成爲了分佈式系統共識性的同義詞。當大學教授分佈式系統共識性的時候,幾乎老是使用 Paxos 做爲算法。Paxos 或許是在全部分佈式系統算法中惟一重要的算法。web

下面會以日誌複製同步爲背景介紹 Paxos,並用日誌拷貝建立副本狀態機(replicated state machine),當說到狀態機時,這裏指的是一個接收一些輸入和生成一些輸出,並保留一些內部狀態的程序或應用,能夠將幾乎全部的程序都當作一個狀態機。這裏的想法是讓一個狀態機高度可靠,能夠經過在多個不一樣的服務器上並行地運行多個狀態機來達到此目的。若是每一個狀態機都以相同的順序接收到一樣的命令集合,那麼它們應該表現出一致的行爲並輸出相同的結果。因此在理想狀態下,若是有些狀態機宕掉了,其餘的還能繼續提供服務。算法

目標:日誌複製同步

image

實現日誌複製同步的目的是讓狀態機以相同的順序來處理命令,首先將命令存入日誌並保證全部的日誌具備相同順序的相同命令。安全

系統是這樣運行的:服務器

若是一個客戶端想要執行狀態機裏的一條命令,它先會將命令傳給其中一個服務器,假設這裏的命令是 X ,服務器會記錄這條命令,並將它傳遞給其餘的服務器,其餘的服務器都會各自記錄這條命令,一旦命令在全部的日誌中都保存有副本,那麼它就能夠傳遞給狀態機供執行,咱們有時會使用詞語 「應用(apply)」 表明命令真實執行的狀況,一旦其中一臺狀態機執行了命令,那麼它的結果就會被返回到客戶端,能夠發如今狀態機上只要日誌是相同的,處理日誌中命令的順序也是一致的,那麼全部狀態機所表現的行爲也是同樣的,這也就是共識模塊要保證的 — 日誌的 副本是正確的。這也就是使用 Paxos 協議的目的。網絡

一個共識模塊最關鍵的特性是:對於一個系統來講,只要有大多數的服務器是可用的,那麼它就能夠提供全部的服務。因此若是咱們有一個 5 臺服務器的集羣,那麼它能夠在僅有 3 臺服務器可用的狀況下,仍然能正常提供服務。因此咱們能夠容忍 5 臺其中的 2 臺宕掉。一般狀況下,集羣的大小會是一個奇數,如 三、5 或 7 。app

Paxos 的失敗模型是一箇中止失敗的模型,也就是說服務器會宕掉,或者它們會停掉和重啓,不過一旦它們運行,它們的行爲老是正確的,它們不會有很差的行爲(即拜占庭將軍問題)。咱們也會假設網絡會丟失消息,或者會存在消息延遲,也就是說可能會存在到達順序會和發送順序不同的狀況。當網絡發生短暫隔斷時,隔斷也能夠被修復,並讓通訊能夠再次容許通訊。當消息傳遞時只要系統在工做,就能保證正確。分佈式

Paxos 方法

image

要分解這個問題有多種方式,首先以最簡單讓人容易想象的共識問題開始 — 基礎 Paxos(Basic Paxos) 或 單度 Paxos。在這個問題下,咱們有一組服務器,其中有些服務器會提議(propose)特定的值。基礎 Paxos 的目的是挑選這些值中惟一的一個,這個被選中的值稱爲(chosen)。全部系統作的就是一次只挑選一個惟一值,它不會挑選第二個值,也不會更改它的選擇。這是咱們能夠想象獲得的最簡單的共識性算法。當人們用到短語共識性算法的時候,一般就是指的這種最簡單的模式。一般咱們談論的 Paxos 也是這個簡單版本的 Paxos 。一旦咱們有這種很是簡單的方式來選擇值,咱們能夠建立日誌,爲多條日誌記錄建立多個實例,這就是 多 Paxos(Multi-Paxos) 。咱們會先解釋 基礎 Paxos ,而後介紹如何根據它來構建 多 Paxos3d

基本 Paxos 的需求

image

在介紹 基礎 Paxos 以前,咱們先來了解一下需求。整體上講有兩個需求,針對於算法的安全性(Safety)和可用性(Liveness)。安全性(Safety)從整體上講,指的是算法在任什麼時候候都不能作很差的事情,在 基礎 Paxos 的語境下,也就是說最多隻能選擇單個值,不能夠選擇第二個值取代第一個值。安全性的第二點是說,若是服務器認爲一個值被選中,那麼它必須真的被服務器繼續選擇了。可用性(Liveness)指的是咱們但願系統最終能作對的事情,僅僅不作很差的事情是不夠的。可用性有兩個屬性,第一個是最終必定會選擇某個提議的值,第二點是服務器最終會知道值已經被選中。這個可用性的前提是大多數的服務器是活着的並能進行合理的通信。日誌

基本 Paxos 的組件

image

基礎 Paxos 有兩個主要的組件, 提議者(Proposers)接受者(Acceptors)提議者(Proposers) 是活動元素,它們會主動作一些事情,一般它們會接收來自客戶端的請求,得到特定的選定值,而後它會傳遞這個值,並讓集羣裏的其餘服務器也達成一致選擇這個值。 接受者(Acceptors) 是被動元素,它們簡單地接收來自於 提議者(Proposers) 的請求並作出響應,能夠把這種響應當成 「投票」 , 提議者(Proposers) 會嘗試得到 接受者(Acceptors) 所投的多數票, 接受者(Acceptors) 會存儲多個狀態,好比可能被選定或未被選定的值,以及響應的 「投票」 結果。最終它仍是會想要知道具體被選定的值是哪一個。不過正如咱們所見,開始的時候,只有 提議者(Proposers) 知道這個值,可是最終 接受者(Acceptors) 仍是須要知道這個值,這樣才能將它傳遞給狀態機。在 Lamport 對於這個問題定義的傳統公式下,還定了另一個元素,稱爲 監聽者(Listeners) 。這些元素想要知道被選定的值,在這個例子中, 監聽者(Listeners) 是處於 接受者(Acceptors) 內的。不只如此,在咱們介紹的這個例子中,每一個服務器都包含一個 提議者(Proposers)接受者(Acceptors) 。想要經過獨立的 提議者(Proposers)接受者(Acceptors) 來構建 Paxos 協議也是可能的,但對於這裏的例子,咱們作以上的前提假設。

稻草人:單接受者(Single Acceptor)

image

接下來會介紹一些咱們想要實現共識性所須要解決的問題。比方說這裏有一個例子,但不幸的是它是不正確的。假設咱們只選擇了一個 接受者(Acceptors) 並讓這個 接受者(Acceptors) 選擇全部的值,因此在這種狀況下,每一個 提議者(Proposers) 都會給 接受者(Acceptors) 發送它的值, 接受者(Acceptors) 會挑選其中的一個,而後將其做爲選定值。儘管這中實現方式很簡單,可是沒法解決 接受者(Acceptors) 可能會崩潰問題,若是 接受者(Acceptors) 在選擇以後立刻就崩潰了,咱們就沒法知道選定的值是什麼,這樣就必須進行重啓。記住算法的目的是隻要大多數節點是可用的,系統就必須能徹底正常工做。

這個辦法行不通。爲了能處理節點失敗的狀況,咱們就必須使用某些仲裁(quorum)的方法,咱們須要有一組 接受者(Acceptors) ,一般是一個奇數,如 3 、5 或 7 。若是一個值被大多數 接受者(Acceptors) 選定,那麼咱們認爲這個值被認爲是選定的。這樣即便在少數服務器崩潰的狀況下,還有多數服務器存留能夠接受值。甚至在接受後崩潰的狀況下,仍是能夠肯定被選定的值。仲裁(quorum)方法可讓咱們在某些服務器崩潰後,仍然能保證集羣能正常工做。

問題:分票

image

不過讓仲裁(quorum)能正常工做須要注意一些問題。

例如,咱們假設每一個 接受者(Acceptors) 都接收它第一次收到的值,而後讓多數票的值獲勝,如上圖咱們能夠看到,也存在沒有任何值是大多數的狀況。服務器 S1 和 S2 接受的值是 red ,服務器 S3 和 S4 接受的值是 blue ,服務器 S5 接受的值是 green 。沒有任何值在五個服務器中的三個達成一致的。這也就意味着 接受者(Acceptors) 有時須要改變他們的想法,在某些狀況下,它們接受了一個值後須要接受另一個不一樣的值。也就是說,幾乎沒法在一輪投票下就能達成一致,每每須要進行幾輪的投票才能達到一致。這裏接受(accepted)並不表明被選定(chosen),一個值只有在集羣大多數節點接受以後才被認爲是選定的。

問題:選擇衝突

image

如今來看另一個問題,當 接受者(Acceptors) 在更混亂的狀況下,它們會接受任何值,但這會帶來兩個問題,我會在本頁和下頁中分別討論這兩個問題。

第一個問題是咱們可能會最終選擇多個值。

例如,服務器 S1 會提議一個值 red ,讓其餘的服務器接受,這樣服務器 S一、S二、S3 接受了這個值,那麼如今被選定的值是 red ,由於它已經被多數服務器(3/5)選擇。不過隨後服務器 S5 來了,提議了一個不一樣的值 blue ,而後讓 接受者(Acceptors) 接受這個值,由於它們會接受任何傳遞給它們的值,那麼此時服務器 S3 接受的值是 blue ,儘管它以前接受的值是 red 。因此如今咱們選擇的值是 blue 。這樣就違背了咱們所定義的基礎的安全屬性的要求 — 咱們只能選定一個值。

這個問題的解決辦法是,若是第二個 提議者(Proposers) 有新的提議,這裏是服務器 S5 ,此時若是已經有選定的值,那麼它就必須放棄它本身的值,並提議當前已經選定的值,因此在這種規約下,在服務器 S5 向其餘服務器發出請求要求接受它的值以前,它就須要查看集羣裏的其餘服務器,看是否有其餘值的存在,若是已經有其餘選定的值,服務器 S5 就須要放棄它本身的值,而後使用 red 來代替,這樣最終就可使 red 成爲選定的值,咱們以第二次選擇爲終結點,可是最終選擇的值是第一次選定的那個值(red)。這也就說,咱們須要使用一個兩段協議(two-phase protocol)。

image

不幸的是,只有兩段協議(two-phase protocol)自己是不夠的。上圖說明了這個問題。

例如,服務器 S1 提議了一個值 red 。它首先檢查其餘的服務器,其餘的服務器尚未接受任何值,因此它開始向其餘服務器發起請求,但願它們能接受本身的值 red 。不過同時,在其餘服務器真正應答以前,另一個服務器 S5 又提議了另外一個值 blue 。這時它也發現尚未其餘服務器肯定了選定值,那麼它就開始發送消息,但願其餘服務器能選擇 blue 。而後,若是這個請求先結束,服務器 S三、S四、S5 接受並選定 blue ,但與此同時,red 值的服務器仍然處於運行中,由於 接受者(Acceptors) 會接受多個值,因此最終能夠看到,會發生仲裁,最終 red 值會被選定,這樣就違背了基本安全的屬性要求。

這個問題的解決辦法是,一旦咱們已經選定了值,任何其餘的競爭性提議必須被放棄。在上面的例子中,咱們就須要服務器 S3 在已經接受了值 blue 後,拒絕對 red 值的接受請求。要想這麼作,咱們會給提議安排順序,新的提議優先於全部提議。也就是說 blue 的請求更晚,它會截斷 red 請求,這樣請求就不會以選擇競爭值爲結束。

因此總結以下:咱們須要一個兩段協議(two-phase protocol)。在發起請求前先進行檢查,而後咱們須要請求有序,這樣就能消除老的請求。

接下來咱們看看如何使請求有序。

請求序號

image

採用的方式是爲每一個請求分配一個惟一的請求序號,使用一個從未被以前請求使用的序號,也就是高的序號要比低序號有更高的優先級,因此當服務器開始執行這個協議, 提議者(Proposers) 必須選擇一個新的請求序號,它必須是惟一的,並且比其餘序號更高。一種實現方式是將兩個值進行拼接,以服務器 ID 爲開始,爲每一個服務器分配一個惟一值,並將其置於請求序號(proposal number)的低位,其餘的服務器都不會生成這個請求。在請求序號高位,咱們放置一個自增循環數,在集羣下全部服務內共享,最近的序號要比以前生成的要高。要想這麼作,全部服務器都必須保存這個最大值,並根據它生成每一個序號,這個值被稱爲 maxRound 。這個值必須被持久化到磁盤或其餘穩定的媒介,以確保能夠在系統崩潰後可以恢復,這樣就能保證在系統崩潰恢復之後,請求序號不會重複。

基礎 Paxos

image

本節會對基礎 Paxos 作一個小結,詳細的內容會在後續介紹

在以前提到過,咱們須要用一個兩段協議(two-phase approach),在第一階段, 提議者(Proposers) 會嘗試讓一個值被選定,它會向全部服務器發送 RPC 請求,咱們將這個請求稱爲 準備(Prepare)。這個請求有兩個目的:首先,它會嘗試找到其餘可能被選定的值,這樣就能夠保證使用被選定的值,而不是本身的值;其次,若是有其餘存在的但未被選中的請求值,它會阻止老的請求,這樣就能夠避免發生競爭。在第二階段,會發起另一個 RPC 請求以及一個選擇好的值,若是這個階段大多數都達成一致,咱們就認爲這個值已經被選定。

基礎 Paxos 協議

image

上圖顯示了完整的基礎 Paxos 協議,讓咱們來看看一個完整的請求過程,正如以前所提到的,整個過程是由 提議者(Proposers) 驅動的。 提議者(Proposers) 會由某個它但願選定的值爲開始,而後會經歷兩輪廣播消息:準備階段(prepare phase)和接受階段(accept phase)。可是首先

(1)須要選擇一個提議序號,緣由在前面已經解釋過,須要提供一個惟一的,以前沒有使用過的數。而後就會進入準備階段(prepare phase),在這個過程當中,

(2) 提議者(Proposers) 會向集羣的全部 接受者(Acceptor) 發起遠程過程調用(remote procedure call),每條消息都包含提議序號(Prepare(n)),

(3)當 接受者(Acceptor) 收到 Prepare(n) 請求,它會作兩件事情:首先,它須要保證永遠不會接受一個提議序號更低的請求,能夠經過保存內部變量(minProposal)的方式來達到目的,隨着時間的推移,這個值會自動增加,若是當前的請求就是具備最高序號的提議,那麼就會更新當前的 minProposal;第二步,就是須要響應並返回接受後的值,若是它以前已接受了某個提議,它會同時記錄接受的值及其序號,並返回它當前接受的、具備最高序號的提議值。

(4) 提議者(Proposers) 會等待大多數 接受者(Acceptor) 的響應,一旦它接收到響應,它會檢查看是否有返回,以及接受的提議值是什麼,這樣它會從全部的響應中挑選最高的提議序號,並用這個具備最高提議序號的值做爲接受值,以替代它所提議的初始值,並用這個值繼續後面的計算。若是沒有 接受者(Acceptor) 返回已接受的提議值,那麼它會使用本身的初始值繼續後面的計算。這裏就完成了協議的第一階段。

(5)這裏 提議者(Proposers) 會發送接受請求(Accept(n, value))到集羣的全部服務器,包括一個提議序號 n ,這個值必須與準備階段的序號值保持一致,以及一個值。這個值能夠是 提議者(Proposers) 所提議的初始值,也能夠是從 接受者(Acceptor) 返回的接受值。這條消息會廣播到集羣的全部服務器。

(6)當集羣服務器接收到這條消息後,它會將接受請求的提議序號與本身保存的提議序號做比較,若是接受到的提議序號沒有保存的序號高,那麼就會拒絕這個接受請求,但若是更高,那麼就會接受這個提議,並記下這個接受請求的提議序號,以及它的值,並更新當前的提議序號,保證它是最大的。不管接受仍是拒絕這個請求, 接受者(Acceptor) 都會返回它當前的提議值。這樣 提議者(Proposers) 就可使用這個返回值來判斷請求是否被接受了。

(7) 提議者(Proposers) 會等待直到它接收到多數響應。一旦收到這些響應,它就會檢查是否有請求被拒絕,它能夠經過返回值和提議序號來進行比較,若是請求被拒絕了,那麼此次提議須要回到第(1)步,從新開始。幸運的是它能夠經過提議序號判斷,那麼下一輪就能夠選取一個更高的提議序號和值,這樣就更有機會在競爭中取勝。大多數狀況下,更好的狀態是 接受者(Acceptor) 都接受了請求,此時提議值被選定,協議結束執行。

爲了保證這個協議能工做正常,集羣必須保證三個值的穩定,minProposal、acceptedProposal 以及 acceptedValue ,它們須要保存在穩定的媒介,如磁盤或閃存中。

基礎 Paxos 示例

接下來用一些例子來講明提議競爭的狀態,以及關鍵點在於第二次提議的準備階段。

總共有三種可能。

1、已經選定了以前的值

image

咱們假設有兩個提議,值分別是 X 和 Y 。一個客戶端請求服務器 S1 讓 X 被選定,另外一個客戶端請求服務器 S5 讓 Y 被選定,上圖的小方格表示特定服務器上的特定請求,因此在 S3 上的小方格 P3.1 表示提議序號 3.1,高位的 3 表示順序數,地位的 5 表示服務器號,因此 P3.1 來自於 S1 。一樣的在 S4 上的 A4.5 X 表示服務器 S4 接受了來自服務器 S5 的請求,以及值 X 。在第二個提議請求來以前,第一個提議請求已經選定了值,也就是說值 X 已經被集羣的大多數服務器所接受。由於第二個提議請求也須要大多數服務器獲得響應,因此必定能夠保證會至少有一個準備(Prepare)請求會到達與前一個請求相同的服務器,這裏是 S3 。因此服務器 S5 會發現已經接受的值 X ,當它響應準備請求(Prepare)時,它會放棄 Y 值,併爲在全部接受(Accept)請求時使用 X 值。因此在這種狀況下,服務器 S5 會成功,而且選定值爲 S1 提議的 X 。

後面兩種狀況的前提是前值沒有被選定的狀況下,第二次請求進入了準備階段(Prepare Phase)

2、前值沒有被選定,但對新提議可見

image

有可能前一次的提議正在處於接受值的過程當中,第二次提議剛好見到了其中的接受值。如上圖所示,服務器 S3 已經接受了第一個提議(P3.1 - A3.1 X),第二個提議者正好看見了這個接受值 X ,由於第二個提議者的準備階段處於前一個提議者接受階段以後(A3.1 X - P4.5)。因此在這種狀況下第二個提議會使用當前已有的值 X ,並放棄 Y 值,並使用來自於 S3 的 X 值做爲選定值。

這與前一種狀況同樣。先後兩次提議請求都成功,並且最終都接受選定了相同的值 X 。

當第二次提議看到 X 值的時候,它並不知道這個值是否真的被選定了,由於它只與大多數服務器發生了對話,因此也有可能 S一、S2 已經完成了 X 的接受,而服務器 S5 並不知道。因此,一旦第二次提議看到前序接受的 X 值,它就必需要假設這個值已經被接受選定,並用這個值做爲它本身的提議值。

3、前值沒有被選定,對新提議也不可見

image

第三個場景是在第二次提議的準備階段到來以前,接受值尚未肯定,並對第二次提議的準備請求不可見的狀況。它可能在別的某個服務器上被接受(S1),可是在第二次提議(S5)的檢查範圍內,沒有前序的值被接受。在這種狀況下,服務器 S5 會使用它本身的值 Y 。最終選定的值也是 Y 。這裏比較幸運的是 S5 成功阻止了 S1 ,S1 的提議至少會在一臺服務器(這裏是 S3)上與 S5 的提議相競爭,因爲 P4.5 有更高的提議序號值,因此這裏 S3 會拒絕接受 X 請求,因此這也會阻止 S1 接受 X ,S1 發起的提議須要從新開始,當再次開始時,會新發起一輪提議,這時它會至少在一臺服務器上發現來自 S5 的提議 Y ,因此 S1 發起的提議最終會以 S5 的提議值 Y 做爲它的接受值。也就是說,最終在集羣內達成了一致,選定值 Y 。

這裏的關鍵點在於這些競爭的提議必須在至少一臺服務器上有重疊,這樣能夠經過它們與服務器通訊的順序來決定最終的結果。要麼在前序提議值對後續請求可見的狀況下,選定前序接受的值,要麼在前序提議值對後續請求者不可見的狀況下,會選定後續提議的接受值。任何一種方式都是安全的。

至此,足以說明 Paxos 協議在競爭狀態下是安全的,不管如何競爭,最終都會選定某一值並達成一致。可是,這並不能說明基礎 Paxos 協議是可用的(Live),可能會發生一組提議相互阻礙的狀況,最終不會有任何選定值。下面會對此進行說明。

可用性(Liveness)

image

競爭提議可能會致使死鎖

假設服務器 S1 成功接收到請求,並處於準備階段(P 3.1)。在接受值 X 以前(A 3.1 X),另一個服務器 S5 正處於它的準備階段(P 3.5),這會阻止前序值的接受(A 3.1 X)。而後 S1 會從新選擇提議序號並再次開始提議過程(P 4.1),假設它正進入了第二輪的準備階段,在接受值以前,服務器 S5 正試圖完成接受值的選定 Y (A 3.5 Y),不過此時由於(P 4.1)的序號高於(A 3.5 Y),因此它阻止了(A 3.5 Y)的接受,這樣 S5 的提議就失敗了,而後 S5 又從新開始下一輪的提議,如此往復,這個過程會無限循環下去。

爲了避免發生死鎖,Paxos 須要以某種補充機制來保證它能夠正確運行。一個簡單的方式是讓服務器等待一會,若是發生接受失敗的狀況,必須返回從新開始。在從新開始以前等待一會,讓提議能有機會完成。可讓集羣下服務器隨機的延遲,從而避免全部服務器都處於相同的等待時間下。在多 Paxos 協議(Multi-Paxos)下,會有些不一樣,咱們會介紹另一種被稱爲領導人選舉(leader election)的機制。保證在同一時間下只有一個 提議者(Proposers) 在工做。

其餘

image

基礎 Paxos 協議也有它的缺陷。一旦值被選定以後,只有一臺服務器(即發起提議的那臺服務器)知道它選定的值是什麼。 接受者(Acceptor) 沒法知道它保存的值是否以及被選中。若是其餘的服務器想要知道被選定的值,它就必須本身執行協議。

參考

參考來源:

2013 Paxos lecture, Diego Ongaro

2013 Raft user study

Wiki: Byzantine fault tolerance

結束

相關文章
相關標籤/搜索