搞懂分佈式技術2:分佈式一致性協議與Paxos,Raft算法

  • 2019 年 12 月 2 日
  • 筆記

本文內容參考網絡,侵刪

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫

https://github.com/h2pl/Java-Tutorial

文章首發於我的個人博客:

www.how2playlife.com

該系列博文會告訴你什麼是分佈式系統,這對後端工程師來說是很重要的一門學問,我們會逐步了解常見的分佈式技術、以及一些較為常見的分佈式系統概念,同時也需要進一步了解zookeeper、分佈式事務、分佈式鎖、負載均衡等技術,以便讓你更完整地了解分佈式技術的具體實戰方法,為真正應用分佈式技術做好準備。

本文較為粗略地講述了一致性協議與兩種一致性算法,更加系統的理論可以參考後面的分佈式系統理論專題文章。

2PC

由於BASE理論需要在一致性和可用性方面做出權衡,因此湧現了很多關於一致性的算法和協議。其中比較著名的有二階提交協議(2 Phase Commitment Protocol),三階提交協議(3 Phase Commitment Protocol)和Paxos算法。本文要介紹的2PC協議,分為兩個階段提交一個事務。並通過協調者和各個參與者的配合,實現分佈式一致性。

兩個階段事務提交協議,由協調者和參與者共同完成。

角色 XA概念 作用 協調者 事務管理器 協調各個參與者,對分佈式事務進行提交或回滾 參與者 資源管理器 分佈式集群中的節點

1. 分佈式事務

分佈式事務是指會涉及到操作多個數據庫的事務,其實就是將對同一庫事務的概念擴大到了對多個庫的事務。目的是為了保證分佈式系統中的數據一致性。

分佈式事務處理的關鍵是:

需要記錄事務在任何節點所做的所有動作;事務進行的所有操作要麼全部提交,要麼全部回滾。

2. XA規範

2.1. XA規範的組成 XA規範是由 X/Open組織(即現在的 Open Group )定義的分佈式事務處理模型。X/Open DTP 模型( 1994 )包括:

應用程序( AP ) 事務管理器( TM ):交易中間件等 資源管理器( RM ):關係型數據庫等 通信資源管理器( CRM ):消息中間件等 2.2. XA規範的定義 XA規範定義了交易中間件與數據庫之間的接口規範(即接口函數),交易中間件用它來通知數據庫事務的開始、結束以及提交、回滾等。而XA接口函數由數據庫廠商提供。

二階提交協議和三階提交協議就是基於XA規範提出的其中,二階段提交就是實現XA分佈式事務的關鍵。

2.3. XA規範編程規範 配置TM,給TM註冊RM作為數據源。其中,一個TM可以註冊多個RM。

AP向TM發起一個全局事務。這時,TM會發送一個XID(全局事務ID)通知各個RM。

AP從TM獲取資源管理器的代理(例如:使用JTA接口,從TM管理的上下文中,獲取出這個TM所管理的RM的JDBC連接或JMS連接)。

AP通過從TM中獲取的連接,間接操作RM進行業務操作。TM在每次AP操作時把XID傳遞給RM,RM正是通過這個XID關聯來操作和事務的關係的。

AP結束全局事務時,TM會通知RM全局事務結束。開始二段提交,也就是prepare – commit的過程。

XA規範的流程,大致如圖所示:

3. 二階段提交(2PC)

3.1. 二階段提交的定義 二階段提交的算法思路可以概括為:每個參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報,決定各參與者是否要提交操作還是中止操作。

所謂的兩個階段分別是:

第一階段:準備階段(投票階段) 第二階段:提交階段(執行階段) 3.1.1. 準備階段 準備階段分為三個步驟:

a. 事務詢問

協調者向所有的參與者詢問,是否準備好了執行事務,並開始等待各參與者的響應。

b. 執行事務

各參與者節點執行事務操作。如果本地事務成功,將Undo和Redo信息記入事務日誌中,但不提交;否則,直接返回失敗,退出執行。

c. 各參與者向協調者反饋事務詢問的響應

如果參與者成功執行了事務操作,那麼就反饋給協調者 Yes響應,表示事務可以執行提交;如果參與者沒有成功執行事務,就返回No給協調者,表示事務不可以執行提交。

3.1.2. 提交階段 在提交階段中,會根據準備階段的投票結果執行2種操作:執行事務提交,中斷事務。

提交事務過程如下:

a. 發送提交請求

協調者向所有參與者發出commit請求。

b. 事務提交

參與者收到commit請求後,會正式執行事務提交操作,並在完成提交之後,釋放整個事務執行期間佔用的事務資源。

c. 反饋事務提交結果

參與者在完成事務提交之後,向協調者發送Ack信息。

d. 事務提交確認

協調者接收到所有參與者反饋的Ack信息後,完成事務。

中斷事務過程如下:

a. 發送回滾請求

協調者向所有參與者發出Rollback請求。

b. 事務回滾

參與者接收到Rollback請求後,會利用其在提交階段種記錄的Undo信息,來執行事務回滾操作。在完成回滾之後,釋放在整個事務執行期間佔用的資源。

c. 反饋事務回滾結果

參與者在完成事務回滾之後,想協調者發送Ack信息。

d. 事務中斷確認

協調者接收到所有參與者反饋的Ack信息後,完成事務中斷。

3.1. 二階段提交的優缺點 優點:原理簡單,實現方便。缺點:同步阻塞,單點問題,數據不一致,容錯性不好。同步阻塞 在二階段提交的過程中,所有的節點都在等待其他節點的響應,無法進行其他操作。這種同步阻塞極大的限制了分佈式系統的性能。

單點問題 協調者在整個二階段提交過程中很重要,如果協調者在提交階段出現問題,那麼整個流程將無法運轉。更重要的是,其他參與者將會處於一直鎖定事務資源的狀態中,而無法繼續完成事務操作。

數據不一致 假設當協調者向所有的參與者發送commit請求之後,發生了局部網絡異常,或者是協調者在尚未發送完所有 commit請求之前自身發生了崩潰,導致最終只有部分參與者收到了commit請求。這將導致嚴重的數據不一致問題。

容錯性不好 如果在二階段提交的提交詢問階段中,參與者出現故障,導致協調者始終無法獲取到所有參與者的確認信息,這時協調者只能依靠其自身的超時機制,判斷是否需要中斷事務。顯然,這種策略過於保守。換句話說,二階段提交協議沒有設計較為完善的容錯機制,任意一個節點是失敗都會導致整個事務的失敗。

小結

對於2PC協議存在的同步阻塞、單點問題,將在下一篇文章的3PC協議中引入解決方案。

3PC

由於二階段提交存在着諸如同步阻塞、單點問題、腦裂等缺陷。所以,研究者們在二階段提交的基礎上做了改進,提出了三階段提交。

與兩階段提交不同的是,三階段提交有兩個改動點。

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

1. 三階段提交的定義

三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。

所謂的三個階段分別是:詢問,然後再鎖資源,最後真正提交。

第一階段:CanCommit 第二階段:PreCommit 第三階段:Do Commit

2. 三階段提交的過程

2.1. 階段一:CanCommit 3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。

a. 事務詢問

協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然後開始等待參與者的響應。

b. 響應反饋

參與者接到CanCommit請求之後,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,並進入預備狀態;否則反饋No。

2.2. 階段二:PreCommit 協調者在得到所有參與者的響應之後,會根據結果執行2種操作:執行事務預提交,或者中斷事務。

2.2.1. 執行事務預提交 a. 發送預提交請求

協調者向所有參與者節點發出 preCommit 的請求,並進入 prepared 狀態。

b. 事務預提交

參與者受到 preCommit 請求後,會執行事務操作,對應 2PC 準備階段中的 「執行事務」,也會 Undo 和 Redo 信息記錄到事務日誌中。

c. 各參與者響應反饋

如果參與者成功執行了事務,就反饋 ACK 響應,同時等待指令:提交(commit) 或終止(abort)。

2.2.2. 中斷事務 a. 發送中斷請求

協調者向所有參與者節點發出 abort 請求 。

b. 中斷事務

參與者如果收到 abort 請求或者超時了,都會中斷事務。

2.3. 階段三:Do Commit 該階段進行真正的事務提交,也可以分為以下兩種情況。

2.3.1. 執行提交 a. 發送提交請求

協調者接收到各參與者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有參與者發送 doCommit 請求。

b. 事務提交

參與者接收到 doCommit 請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。

c. 響應反饋

事務提交完之後,向協調者發送 ACK 響應。

d. 完成事務

協調者接收到所有參與者的 ACK 響應之後,完成事務。

2.3.2. 中斷事務 協調者沒有接收到參與者發送的 ACK 響應(可能是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

a. 發送中斷請求

協調者向所有參與者發送 abort 請求。

b. 事務回滾

參與者接收到 abort 請求之後,利用其在階段二記錄的 undo 信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。

c. 反饋結果

參與者完成事務回滾之後,向協調者發送 ACK 消息。

d. 中斷事務

協調者接收到參與者反饋的 ACK 消息之後,完成事務的中斷。

3. 小結

3.1. 三階段提交的優點 相對於二階段提交,三階段提交主要解決的單點故障問題,並減少了阻塞的時間。

因為一旦參與者無法及時收到來自協調者的信息之後,他會默認執行 commit。而不會一直持有事務資源並處於阻塞狀態。

3.2. 三階段提交的缺點 三階段提交也會導致數據一致性問題。由於網絡原因,協調者發送的 abort 響應沒有及時被參與者接收到,那麼參與者在等待超時之後執行了 commit 操作。

這樣就和其他接到 abort 命令並執行回滾的參與者之間存在數據不一致的情況。

四、Paxos

用於達成共識性問題,即對多個節點產生的值,該算法能保證只選出唯一一個值。

主要有三類節點:

提議者(Proposer):提議一個值;接受者(Acceptor):對每個提議進行投票;告知者(Learner):被告知投票的結果,不參與投票過程。

執行過程 規定一個提議包含兩個字段:[n, v],其中 n 為序號(具有唯一性),v 為提議值。

下圖演示了兩個 Proposer 和三個 Acceptor 的系統中運行該算法的初始過程,每個 Proposer 都會向所有 Acceptor 發送提議請求。

當 Acceptor 接收到一個提議請求,包含的提議為 [n1, v1],並且之前還未接收過提議請求,那麼發送一個提議響應,設置當前接收到的提議為 [n1, v1],並且保證以後不會再接受序號小於 n1 的提議。

如下圖,Acceptor X 在收到 [n=2, v=8] 的提議請求時,由於之前沒有接收過提議,因此就發送一個 [no previous] 的提議響應,設置當前接收到的提議為 [n=2, v=8],並且保證以後不會再接受序號小於 2 的提議。其它的 Acceptor 類似。

如果 Acceptor 接收到一個提議請求,包含的提議為 [n2, v2],並且之前已經接收過提議 [n1, v1]。如果 n1 > n2,那麼就丟棄該提議請求;否則,發送提議響應,該提議響應包含之前已經接收過的提議 [n1, v1],設置當前接收到的提議為 [n2, v2],並且保證以後不會再接受序號小於 n2 的提議。

如下圖,Acceptor Z 收到 Proposer A 發來的 [n=2, v=8] 的提議請求,由於之前已經接收過 [n=4, v=5] 的提議,並且 n > 2,因此就拋棄該提議請求;Acceptor X 收到 Proposer B 發來的 [n=4, v=5] 的提議請求,因為之前接收到的提議為 [n=2, v=8],並且 2 <= 4,因此就發送 [n=2, v=8] 的提議響應,設置當前接收到的提議為 [n=4, v=5],並且保證以後不會再接受序號小於 4 的提議。Acceptor Y 類似。

當一個 Proposer 接收到超過一半 Acceptor 的提議響應時,就可以發送接受請求。

Proposer A 接收到兩個提議響應之後,就發送 [n=2, v=8] 接受請求。該接受請求會被所有 Acceptor 丟棄,因為此時所有 Acceptor 都保證不接受序號小於 4 的提議。

Proposer B 過後也收到了兩個提議響應,因此也開始發送接受請求。需要注意的是,接受請求的 v 需要取它收到的最大 v 值,也就是 8。因此它發送 [n=4, v=8] 的接受請求。

Acceptor 接收到接受請求時,如果序號大於等於該 Acceptor 承諾的最小序號,那麼就發送通知給所有的 Learner。當 Learner 發現有大多數的 Acceptor 接收了某個提議,那麼該提議的提議值就被 Paxos 選擇出來。

約束條件

  1. 正確性 指只有一個提議值會生效。

因為 Paxos 協議要求每個生效的提議被多數 Acceptor 接收,並且 Acceptor 不會接受兩個不同的提議,因此可以保證正確性。

  1. 可終止性 指最後總會有一個提議生效。

Paxos 協議能夠讓 Proposer 發送的提議朝着能被大多數 Acceptor 接受的那個提議靠攏,因此能夠保證可終止性。

BasicPaxos與FastPaxos

1 Paxos算法 1.1 基本定義 算法中的參與者主要分為三個角色,同時每個參與者又可兼領多個角色:

⑴proposer 提出提案,提案信息包括提案編號和提議的value;

⑵acceptor 收到提案後可以接受(accept)提案;

⑶learner 只能"學習"被批准的提案;

算法保重一致性的基本語義:

⑴決議(value)只有在被proposers提出後才能被批准(未經批准的決議稱為"提案(proposal)");

⑵在一次Paxos算法的執行實例中,只批准(chosen)一個value;

⑶learners只能獲得被批准(chosen)的value;

有上面的三個語義可演化為四個約束:

⑴P1:一個acceptor必須接受(accept)第一次收到的提案;

⑵P2a:一旦一個具有value v的提案被批准(chosen),那麼之後任何acceptor 再次接受(accept)的提案必須具有value v;

⑶P2b:一旦一個具有value v的提案被批准(chosen),那麼以後任何 proposer 提出的提案必須具有value v;

⑷P2c:如果一個編號為n的提案具有value v,那麼存在一個多數派,要麼他們中所有人都沒有接受(accept)編號小於n的任何提案,要麼他們已經接受(accpet)的所有編號小於n的提案中編號最大的那個提案具有value v;

1.2 基本算法(basic paxos) 算法(決議的提出與批准)主要分為兩個階段:

1. prepare階段:

(1). 當Porposer希望提出方案V1,首先發出prepare請求至大多數Acceptor。Prepare請求內容為序列號<SN1>;

(2). 當Acceptor接收到prepare請求<SN1>時,檢查自身上次回復過的prepare請求<SN2>

a). 如果SN2>SN1,則忽略此請求,直接結束本次批准過程;

b). 否則檢查上次批准的accept請求<SNx,Vx>,並且回復<SNx,Vx>;如果之前沒有進行過批准,則簡單回復<OK>;

2. accept批准階段:

(1a). 經過一段時間,收到一些Acceptor回復,回復可分為以下幾種:

a). 回複數量滿足多數派,並且所有的回復都是<OK>,則Porposer發出accept請求,請求內容為議案<SN1,V1>;

b). 回複數量滿足多數派,但有的回復為:<SN2,V2>,<SN3,V3>……則Porposer找到所有回復中超過半數的那個,假設為<SNx,Vx>,則發出accept請求,請求內容為議案<SN1,Vx>;

c). 回複數量不滿足多數派,Proposer嘗試增加序列號為SN1+,轉1繼續執行;

(1b). 經過一段時間,收到一些Acceptor回復,回復可分為以下幾種:

a). 回複數量滿足多數派,則確認V1被接受;

b). 回複數量不滿足多數派,V1未被接受,Proposer增加序列號為SN1+,轉1繼續執行;

(2). 在不違背自己向其他proposer的承諾的前提下,acceptor收到accept 請求後即接受並回復這個請求。

1.3 算法優化(fast paxos) Paxos算法在出現競爭的情況下,其收斂速度很慢,甚至可能出現活鎖的情況,例如當有三個及三個以上的proposer在發送prepare請求後,很難有一個proposer收到半數以上的回復而不斷地執行第一階段的協議。因此,為了避免競爭,加快收斂的速度,在算法中引入了一個Leader這個角色,在正常情況下同時應該最多只能有一個參與者扮演Leader角色,而其它的參與者則扮演Acceptor的角色,同時所有的人又都扮演Learner的角色。

在這種優化算法中,只有Leader可以提出議案,從而避免了競爭使得算法能夠快速地收斂而趨於一致,此時的paxos算法在本質上就退變為兩階段提交協議。但在異常情況下,系統可能會出現多Leader的情況,但這並不會破壞算法對一致性的保證,此時多個Leader都可以提出自己的提案,優化的算法就退化成了原始的paxos算法。

一個Leader的工作流程主要有分為三個階段:

(1).學習階段 向其它的參與者學習自己不知道的數據(決議);

(2).同步階段 讓絕大多數參與者保持數據(決議)的一致性;

(3).服務階段 為客戶端服務,提議案;

1.3.1 學習階段 當一個參與者成為了Leader之後,它應該需要知道絕大多數的paxos實例,因此就會馬上啟動一個主動學習的過程。假設當前的新Leader早就知道了1-134、138和139的paxos實例,那麼它會執行135-137和大於139的paxos實例的第一階段。如果只檢測到135和140的paxos實例有確定的值,那它最後就會知道1-135以及138-140的paxos實例。

1.3.2 同步階段 此時的Leader已經知道了1-135、138-140的paxos實例,那麼它就會重新執行1-135的paxos實例,以保證絕大多數參與者在1-135的paxos實例上是保持一致的。至於139-140的paxos實例,它並不馬上執行138-140的paxos實例,而是等到在服務階段填充了136、137的paxos實例之後再執行。這裡之所以要填充間隔,是為了避免以後的Leader總是要學習這些間隔中的paxos實例,而這些paxos實例又沒有對應的確定值。

1.3.4 服務階段 Leader將用戶的請求轉化為對應的paxos實例,當然,它可以並發的執行多個paxos實例,當這個Leader出現異常之後,就很有可能造成paxos實例出現間斷。

1.3.5 問題 (1).Leader的選舉原則

(2).Acceptor如何感知當前Leader的失敗,客戶如何知道當前的Leader

(3).當出現多Leader之後,如何kill掉多餘的Leader

(4).如何動態的擴展Acceptor

五、Raft

Raft 和 Paxos 類似,但是更容易理解,也更容易實現。

Raft 主要是用來競選主節點。

單個 Candidate 的競選 有三種節點:Follower、Candidate 和 Leader。Leader 會周期性的發送心跳包給 Follower。每個 Follower 都設置了一個隨機的競選超時時間,一般為 150ms~300ms,如果在這個時間內沒有收到 Leader 的心跳包,就會變成 Candidate,進入競選階段。

下圖表示一個分佈式系統的最初階段,此時只有 Follower,沒有 Leader。Follower A 等待一個隨機的競選超時時間之後,沒收到 Leader 發來的心跳包,因此進入競選階段。

此時 A 發送投票請求給其它所有節點。

其它節點會對請求進行回復,如果超過一半的節點回復了,那麼該 Candidate 就會變成 Leader。

之後 Leader 會周期性地發送心跳包給 Follower,Follower 接收到心跳包,會重新開始計時。

多個 Candidate 競選 如果有多個 Follower 成為 Candidate,並且所獲得票數相同,那麼就需要重新開始投票,例如下圖中 Candidate B 和 Candidate D 都獲得兩票,因此需要重新開始投票。

當重新開始投票時,由於每個節點設置的隨機競選超時時間不同,因此能下一次再次出現多個 Candidate 並獲得同樣票數的概率很低。

日誌複製 來自客戶端的修改都會被傳入 Leader。注意該修改還未被提交,只是寫入日誌中。

Leader 會把修改複製到所有 Follower。

Leader 會等待大多數的 Follower 也進行了修改,然後才將修改提交。

此時 Leader 會通知的所有 Follower 讓它們也提交修改,此時所有節點的值達成一致。

參考資料

倪超. 從 Paxos 到 ZooKeeper : 分佈式一致性原理與實踐 [M]. 電子工業出版社, 2015. What is CAP theorem in distributed database system? NEAT ALGORITHMS – PAXOS Raft: Understandable Distributed Consensus Paxos By Example