從分佈式一致性到共識機制(一)Paxos算法

從分佈式系統的CAP理論出發,關注分佈式一致性,以及區塊鏈的共識問題及解決。node

區塊鏈首先是一個大規模分佈式系統,共識問題本質就是分佈式系統的一致性問題,可是又有很大的不一樣。
工程開發中,認爲系統中存在故障(fault),但不存在惡意(corrupt)節點,而區塊鏈,特別是公開鏈是落地到物理世界中,涉及到人性和利益關係,不可避免的存在信任以及惡意攻擊問題。git

分佈式一致性處理的是節點失效狀況(便可能消息丟失或重複,但無錯誤消息)的共識達成(Consensus)問題,主要是Paxos算法及衍生的Raft算法。github

1、分佈式系統的挑戰

 

 

CAP理論的核心思想是任何基於網絡的數據共享系統最多隻能知足數據一致性(Consistency)、可用性(Availability)和網絡分區容忍(Partition Tolerance)三個特性中的兩個。web

  • Consistency 一致性
    一致性指「all nodes see the same data at the same time」,即更新操做成功並返回客戶端完成後,全部節點在同一時間的數據徹底一致。等同於全部節點擁有數據的最新版本。算法

  • Availability 可用性數據庫

可用性指「Reads and writes always succeed」,即服務一直可用,並且是正常響應時間。
對於一個可用性的分佈式系統,每個非故障的節點必須對每個請求做出響應。也就是,該系統使用的任何算法必須最終終止。當同時要求分區容忍性時,這是一個很強的定義:即便是嚴重的網絡錯誤,每一個請求必須終止。promise

  • Partition Tolerance 分區容忍性

Tolerance也能夠翻譯爲容錯,分區容忍性具體指「the system continues to operate despite arbitrary message loss or failure of part of the system」,即系統容忍網絡出現分區,分區之間網絡不可達的狀況,分區容忍性和擴展性緊密相關,Partition Tolerance特指在遇到某節點或網絡分區故障的時候,仍然可以對外提供知足一致性和可用性的服務。網絡

提升分區容忍性的辦法就是一個數據項複製到多個節點上,那麼出現分區以後,這一數據項就可能分佈到各個區裏。分區容忍就提升了。然而,要把數據複製到多個節點,就會帶來一致性的問題,就是多個節點上面的數據多是不一致的。要保證一致,每次寫操做就都要等待所有節點寫成功,而這等待又會帶來可用性的問題。併發

 

如圖,Client A能夠發送指令到Server而且設置更新X的值,Client 1從Server讀取該值,在單點狀況下,即沒有網絡分區的狀況下,或者經過簡單的事務機制,能夠保證Client 1讀到的始終是最新的值,不存在一致性的問題。分佈式

若是在系統中增長一組節點,Write操做可能在Server 1上成功,在Server 1上失敗,這時候對於Client 1和Client 2,就會讀取到不一致的值,出現不一致。若是要保持x值的一致性,Write操做必須同時失敗,下降系統的可用性。

能夠看到,在分佈式系統中,同時知足CAP定律中「一致性」、「可用性」和「分區容錯性」三者是不可能的。

在一般的分佈式系統中,爲了保證數據的高可用,一般會將數據保留多個副本(replica),網絡分區是既成的現實,因而只能在可用性和一致性二者間作出選擇。CAP理論關注的是絕對狀況下,在工程上,可用性和一致性並非徹底對立,咱們關注的每每是如何在保持相對一致性的前提下,提升系統的可用性。

2、數據一致性模型

在互聯網領域的絕大多數的場景,都須要犧牲強一致性來換取系統的高可用性,系統每每只須要保證「最終一致性」,只要這個最終時間是在用戶能夠接受的範圍內便可。

對於一致性,能夠分爲從服務端和客戶端兩個不一樣的視角,即內部一致性和外部一致性。
沒有全局時鐘,絕對的內部一致性是沒有意義的,通常來講,咱們討論的一致性都是外部一致性。外部一致性主要指的是多併發訪問時更新過的數據如何獲取的問題。

強一致性:
當更新操做完成以後,任何多個後續進程或者線程的訪問都會返回最新的更新過的值。這種是對用戶最友好的,就是用戶上一次寫什麼,下一次就保證能讀到什麼。根據 CAP 理論,這種實現須要犧牲可用性。

弱一致性:
系統並不保證續進程或者線程的訪問都會返回最新的更新過的值。用戶讀到某一操做對系統特定數據的更新須要一段時間,咱們稱這段時間爲「不一致性窗口」。系統在數據寫入成功以後,不承諾當即能夠讀到最新寫入的值,也不會具體的承諾多久以後能夠讀到。

最終一致性:
是弱一致性的一種特例。系統保證在沒有後續更新的前提下,系統最終返回上一次更新操做的值。在沒有故障發生的前提下,不一致窗口的時間主要受通訊延遲,系統負載和複製副本的個數影響。

最終一致性模型根據其提供的不一樣保證能夠劃分爲更多的模型,包括因果一致性和讀自寫一致性等。

3、兩階段和三階段提交

在分佈式系統中,各個節點之間在物理上相互獨立,經過網絡進行溝通和協調。
典型的好比關係型數據庫,因爲存在事務機制,能夠保證每一個獨立節點上的數據操做能夠知足ACID。
可是,相互獨立的節點之間沒法準確的知道其餘節點中的事務執行狀況,因此兩臺機器理論上沒法達到一致的狀態。

若是想讓分佈式部署的多臺機器中的數據保持一致性,那麼就要保證在全部節點的數據寫操做,要不所有都執行,要麼所有的都不執行。
可是,一臺機器在執行本地事務的時候沒法知道其餘機器中的本地事務的執行結果。因此節點並不知道本次事務到底應該commit仍是 roolback。

因此實現分佈式事務,須要讓當前節點知道其餘節點的任務執行狀態。常規的解決辦法就是引入一個「協調者」的組件來統一調度全部分佈式節點的執行。著名的是二階段提交協議(Two Phase Commitment Protocol)和三階段提交協議(Three Phase Commitment Protocol)。

1.二階段提交協議

Two Phase指的是Commit-request階段Commit階段。

  • 請求階段
    在請求階段,協調者將通知事務參與者準備提交或取消事務,而後進入表決過程。
    在表決過程當中,參與者將告知協調者本身的決策:贊成(事務參與者本地做業執行成功)或取消(本地做業執行故障)。

  • 提交階段
    在該階段,協調者將基於第一個階段的投票結果進行決策:提交或取消。
    當且僅當全部的參與者贊成提交事務協調者才通知全部的參與者提交事務,不然協調者將通知全部的參與者取消事務。參與者在接收到協調者發來的消息後將執行響應的操做。

 
 

能夠看出,兩階段提交協議存在明顯的問題:

  • 同步阻塞
    執行過程當中,全部參與節點都是事務獨佔狀態,當參與者佔有公共資源時,第三方節點訪問公共資源被阻塞。

  • 單點問題
    一旦協調者發生故障,參與者會一直阻塞下去。

  • 數據不一致性
    在第二階段中,假設協調者發出了事務commit的通知,可是由於網絡問題該通知僅被一部分參與者所收到並執行commit,其他的參與者沒有收到通知一直處於阻塞狀態,這段時間就產生了數據的不一致性。

2.三階段提交協議

Three Phase分別爲CanCommit、PreCommit、DoCommit。

 

三階段提交針對兩階段提交作了改進:

  • 引入超時機制。在2PC中,只有協調者擁有超時機制,3PC同時在協調者和參與者中都引入超時機制。
  • 在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段以前各參與節點的狀態是一致的。

4、Paxos算法的提出

二階段提交仍是三階段提交都沒法很好的解決分佈式的一致性問題,直到Paxos算法的提出,Paxos協議由Leslie Lamport最先在1990年提出,目前已經成爲應用最廣的分佈式一致性算法。

Google Chubby的做者Mike Burrows說過這個世界上只有一種一致性算法,那就是Paxos,其它的算法都是殘次品。

1.節點角色

Paxos 協議中,有三類節點:

  • Proposer:提案者

Proposer 能夠有多個,Proposer 提出議案(value)。所謂 value,在工程中能夠是任何操做,例如「修改某個變量的值爲某個值」、「設置當前 primary 爲某個節點」等等。Paxos 協議中統一將這些操做抽象爲 value。
不一樣的 Proposer 能夠提出不一樣的甚至矛盾的 value,例如某個 Proposer 提議「將變量 X 設置爲 1」,另外一個 Proposer 提議「將變量 X 設置爲 2」,但對同一輪 Paxos 過程,最多隻有一個 value 被批准。

  • Acceptor:批准者

Acceptor 有 N 個,Proposer 提出的 value 必須得到超過半數(N/2+1)的
Acceptor 批准後才能經過。Acceptor 之間徹底對等獨立。

  • Learner:學習者

Learner 學習被批准的 value。所謂學習就是經過讀取各個 Proposer 對 value 的選擇結果,若是某個 value 被超過半數 Proposer 經過,則 Learner 學習到了這個 value。

這裏相似 Quorum 議會機制,某個 value 須要得到 W=N/2 + 1 的 Acceptor 批准,Learner 須要至少讀取 N/2+1 個 Accpetor,至多讀取 N 個 Acceptor 的結果後,能學習到一個經過的 value。

2.約束條件

上述三類角色只是邏輯上的劃分,實踐中一個節點能夠同時充當這三類角色。有些文章會添加一個Client角色,做爲產生議題者,實際不參與選舉過程。

Paxos中 proposer 和 acceptor 是算法的核心角色,paxos 描述的就是在一個由多個 proposer 和多個 acceptor 構成的系統中,如何讓多個 acceptor 針對 proposer 提出的多種提案達成一致的過程,而 learner 只是「學習」最終被批准的提案。

Paxos協議流程還須要知足幾個約束條件:

  • Acceptor必須接受它收到的第一個提案;
  • 若是一個提案的v值被大多數Acceptor接受過,那後續的全部被接受的提案中也必須包含v值(v值能夠理解爲提案的內容,提案由一個或多個v和提案編號組成);
  • 若是某一輪 Paxos 協議批准了某個 value,則之後各輪 Paxos 只能批准這個value;

每輪 Paxos 協議分爲準備階段和批准階段,在這兩個階段 Proposer 和 Acceptor 有各自的處理流程。

Proposer與Acceptor之間的交互主要有4類消息通訊,以下圖:

 
 

這4類消息對應於paxos算法的兩個階段4個過程:

  • Phase 1
    a) proposer向網絡內超過半數的acceptor發送prepare消息
    b) acceptor正常狀況下回復promise消息
  • Phase 2
    a) 在有足夠多acceptor回覆promise消息時,proposer發送accept消息
    b) 正常狀況下acceptor回覆accepted消息

3.選舉過程

 
 
  • Phase 1 準備階段

Proposer 生成全局惟一且遞增的ProposalID,向 Paxos 集羣的全部機器發送 Prepare請求,這裏不攜帶value,只攜帶N即ProposalID 。

Acceptor 收到 Prepare請求 後,判斷:收到的ProposalID 是否比以前已響應的全部提案的N大:
若是是,則:
(1) 在本地持久化 N,可記爲Max_N。
(2) 回覆請求,並帶上已Accept的提案中N最大的value(若此時尚未已Accept的提案,則返回value爲空)。
(3) 作出承諾:不會Accept任何小於Max_N的提案。

若是否:不回覆或者回復Error。

  • Phase 2 選舉階段

P2a:Proposer 發送 Accept
通過一段時間後,Proposer 收集到一些 Prepare 回覆,有下列幾種狀況:
(1) 回覆數量 > 一半的Acceptor數量,且全部的回覆的value都爲空,則Porposer發出accept請求,並帶上本身指定的value。
(2) 回覆數量 > 一半的Acceptor數量,且有的回覆value不爲空,則Porposer發出accept請求,並帶上回復中ProposalID最大的value(做爲本身的提案內容)。
(3) 回覆數量 <= 一半的Acceptor數量,則嘗試更新生成更大的ProposalID,再轉P1a執行。

P2b:Acceptor 應答 Accept
Accpetor 收到 Accpet請求 後,判斷:
(1) 收到的N >= Max_N (通常狀況下是 等於),則回覆提交成功,並持久化N和value。
(2) 收到的N < Max_N,則不回覆或者回復提交失敗。

P2c: Proposer 統計投票
通過一段時間後,Proposer 收集到一些 Accept 回覆提交成功,有幾種狀況:
(1) 回覆數量 > 一半的Acceptor數量,則表示提交value成功。此時,能夠發一個廣播給全部Proposer、Learner,通知它們已commit的value。
(2) 回覆數量 <= 一半的Acceptor數量,則 嘗試 更新生成更大的 ProposalID,再轉P1a執行。
(3) 收到一條提交失敗的回覆,則嘗試更新生成更大的 ProposalID,再轉P1a執行。

4.相關討論

Paxos算法的核心思想:
(1)引入了多個Acceptor,單個Acceptor就相似2PC中協調者的單點問題,避免故障
(2)Proposer用更大ProposalID來搶佔臨時的訪問權,能夠對比2PC協議,防止其中一個Proposer崩潰宕機產生阻塞問題
(3)保證一個N值,只有一個Proposer能進行到第二階段運行,Proposer按照ProposalID遞增的順序依次運行
(3) 新ProposalID的proposer好比認同前面提交的Value值,遞增的ProposalID的Value是一個繼承關係

爲何在Paxos運行過程當中,半數之內的Acceptor失效都能運行?
(1) 若是半數之內的Acceptor失效時 還沒肯定最終的value,此時,全部Proposer會競爭 提案的權限,最終會有一個提案會 成功提交。以後,會有半過數的Acceptor以這個value提交成功。
(2) 若是半數之內的Acceptor失效時 已肯定最終的value,此時,全部Proposer提交前 必須以 最終的value 提交,此值也能夠被獲取,並再也不修改。

如何產生惟一的編號呢?
在《Paxos made simple》中提到的是讓全部的Proposer都從不相交的數據集合中進行選擇,例如系統有5個Proposer,則可爲每個Proposer分配一個標識j(0~4),則每個proposer每次提出決議的編號能夠爲5*i + j(i能夠用來表示提出議案的次數)。

推薦larmport和paxos相關的三篇論文:
The Part-Time Parliament
Paxos made simple
Fast Paxos

2PC/3PC和Paxos協議是經典的分佈式協議,理解了它們之後,學習其餘分佈式協議會簡單不少。

 

參考:
CAP theorem
淺談分佈式系統的基本問題:可用性與一致性
分佈式系統入門到實戰
圖解分佈式一致性協議Paxos
Paxos協議學習小結

相關文章
相關標籤/搜索