一致性協議算法-2PC、3PC、Paxos、Raft、ZAB、NWR詳解

點擊上方 IT牧場 ,選擇 置頂或者星標技術乾貨每日送達!

來源:r6d.cn/VMW9


背景

在常見的分佈式系統中,總會發生諸如機器宕機或網絡異常(包括消息的延遲、丟失、重複、亂序,還有網絡分區)等情況。

一致性算法需要解決的問題就是如何在一個可能發生上述異常的分佈式系統中,快速且正確地在集羣內部對某個數據的值達成一致,並且保證不論發生以上任何異常,都不會破壞整個系統的一致性。


CAP 定理

CAP 理論告訴我們,一個分佈式系統不可能同時滿足一致性(C:Consistency),可用性(A: Availability)和分區容錯性(P:Partition tolerance)這三個基本需求,最多隻能同時滿足其中的2個。

Base 理論

BASE:全稱:Basically Available(基本可用),Soft state(軟狀態),和 Eventually consistent(最終一致性)。

Base 理論是對 CAP 中一致性和可用性權衡的結果,其來源於對大型互聯網分佈式實踐的總結,是基於 CAP 定理逐步演化而來的。其核心思想是:既是無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。

解釋一下:什麼是軟狀態呢?相對於原子性而言,要求多個節點的數據副本都是一致的,這是一種 “硬狀態”。軟狀態指的是:允許系統中的數據存在中間狀態,並認爲該狀態不影響系統的整體可用性,即允許系統在多個不同節點的數據副本存在數據延時。

2PC

Two-Phase Commit,事務的提交過程分成了兩個階段來進行處理。

2PC 階段一

1.事務詢問

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

1.執行事務

各參與者節點執行事務操作,並將 Undo 和 Redo 信息記入事務日誌中

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

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

2PC 階段二

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

執行事務提交步驟如下:

•發送提交請求:協調者向所有參與者發出 commit 請求。•事務提交:參與者收到 commit 請求後,會正式執行事務提交操作,並在完成提交之後釋放整個事務執行期間佔用的事務資源。•反饋事務提交結果:參與者在完成事務提交之後,向協調者發送 Ack 信息。•協調者接收到所有參與者反饋的 Ack 信息後,完成事務。

中斷事務步驟如下:

•發送回滾請求:協調者向所有參與者發出 Rollback 請求。•事務回滾:參與者接收到 Rollback 請求後,會利用其在階段一種記錄的 Undo 信息來執行事務回滾操作,並在完成回滾之後釋放在整個事務執行期間佔用的資源。•反饋事務回滾結果:參與者在完成事務回滾之後,想協調者發送 Ack 信息。•中斷事務:協調者接收到所有參與者反饋的 Ack 信息後,完成事務中斷。

從上面的邏輯可以看出,二階段提交就做了2個事情:投票,執行。

舉個例子:

二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:

1、同步阻塞問題。執行過程中,所有參與節點都是事務阻塞型的。當參與者佔有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。

2、單點故障。由於協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那麼所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因爲協調者宕機導致的參與者處於阻塞狀態的問題)

3、數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求之後,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之後就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分佈式系統便出現了數據部一致性的現象。

4、二階段無法解決的問題:協調者再發出commit消息之後宕機,而唯一接收到這條消息的參與者同時也宕機了。那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。

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

3PC

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

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

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

也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分爲二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。

CanCommit階段

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

1.事務詢問 協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然後開始等待參與者的響應。2.響應反饋 參與者接到CanCommit請求之後,正常情況下,如果其自身認爲可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No

PreCommit階段

協調者根據canCommit階段參與者的反應情況來決定是否可以繼續事務的PreCommit操作。根據響應情況,有以下兩種可能。

假如協調者在CanCommit階段從所有的參與者獲得的反饋都是Yes響應,那麼就會執行事務的預執行。

1.發送預提交請求 協調者向參與者發送PreCommit請求,並進入Prepared階段。2.事務預提交 參與者接收到PreCommit請求後,會執行事務操作,並將undo和redo信息記錄到事務日誌中。3.響應反饋 如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

假如canCommit階段有任何一個參與者向協調者發送了No響應,或者等待超時之後,協調者都沒有接到參與者的響應,那麼就執行事務的中斷。

1.發送中斷請求 協調者向所有參與者發送abort請求。2.中斷事務 參與者收到來自協調者的abort請求之後(或超時之後,仍未收到協調者的請求),執行事務的中斷。

doCommit階段

該階段進行真正的事務提交,也可以分爲以下兩種情況。

執行提交

1.發送提交請求 協調接在preCommit階段收到參與者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有參與者發送doCommit請求。2.事務提交 參與者接收到doCommit請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。3.響應反饋 事務提交完之後,向協調者發送Ack響應。4.完成事務 協調者接收到所有參與者的ack響應之後,完成事務。

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

1.發送中斷請求 協調者向所有參與者發送abort請求2.事務回滾 參與者接收到abort請求之後,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。3.反饋結果 參與者完成事務回滾之後,向協調者發送ACK消息4.中斷事務 協調者接收到參與者反饋的ACK消息之後,執行事務的中斷。

在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者abort請求時,會在等待超時之後,會繼續進行事務的提交。(其實這個應該是基於概率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那麼協調者產生PreCommit請求的前提條件是他在第二階段開始之前,收到所有參與者的CanCommit響應都是Yes。(一旦參與者收到了PreCommit,意味他知道大家其實都同意修改了)所以,一句話概括就是,當進入第三階段時,由於網絡超時等原因,雖然參與者沒有收到commit或者abort響應,但是他有理由相信:成功提交的機率很大。)

小結

沒有任何事情是完美的。特別是在分佈式的情況下。事實上,分佈式在某個程度上其實是人類社會發展的一個極佳寫真。因爲人類社會中個體的可靠性顯然比分佈式系統節點的可靠性要低很多。

三階段提交也不完美。但是它比兩階段好。

兩階段的問題可以這樣分解:

•協調者出錯,參與者也出錯;•協調者出錯,參與者不出錯;•協調者不出錯,參與者出錯;•協調者不出錯,參與者也不出錯。

顯然第4種不是問題。所以實際上只有3個問題。而問題2可以通過簡單地NEW一個新的協調者來解決。問題3的錯則顯然正是兩階段提交協議的解決目標,所以也沒有問題。有問題的只有協調者出錯,參與者也出錯的問題。

無論2pc還是3pc只有在以下的情況纔會出現數據不一致性:協調者掛了,備份協調者恢復協議時,某個參與者掛了,在剩下參與者都是“YES”的狀態下, 備份協調者沒法分辨掛了的參與者狀態。(此處掛了可理解爲宕機或者時網絡連不上)

接下來將對上面段落使用一些替代詞:協調者A,備份協調者B,掛了參與者C

•在2pc中,B需要分辨兩種情形:1是C提交了事務(phase 2),2是C在原始投票是abort(phase 1)。如果B決定abort,會違反情形1,如果決定commit,則違背C在表決時的意願,這個時候需要blocking 。(上面的"YES", 在這裏可認爲剩下的參與者在原始投票都是yes。)•在3pc中,B需要分辨兩種情形:1是C提交了事務(phase 3),2是B不知道C有沒有收到prepare commit(phase 2),在這種情況下,因爲我們已經phase 1對大家的意願進行了收集,得到的都是commit,所以此處會用比較激進做法,非blocking,所以纔有上面的腦裂容錯策略,這樣也會降低阻塞範圍。

Paxos算法

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

Paxos在原作者的《Paxos Made Simple》中內容是比較精簡的:

第一階段

(a) 提議者選擇一個提議編號n,並向大多數接受者發送一個編號n的準備請求。

(b) 如果承兌人收到的準備請求的編號n大於其已答覆的任何準備請求的編號,則承兌人對該請求作出答覆,並承諾不接受任何編號小於n且其已接受的編號最高的提案(如有)。

第二階段

(a) 如果提案人從大多數接受人處收到對其準備請求(編號n)的響應,則它向這些接受人中的每一個發送一個接受請求,請求編號n的提案,其值爲v,其中v是響應中編號最高的提案的值,或者如果響應報告沒有提案,則v是任何值。

(b) 如果承兌人收到編號爲n的提案的接受請求,則除非承兌人已對編號大於n的準備請求作出響應,否則接受該提案。

翻譯一下:

Paxos問題指分佈式系統中存在故障fault,但不存在惡意corrupt節點場景(消息可能丟失但不會造假)下的共識達成(Consensus)問題。

Paxos是第一個被證明的共識算法,原理基於兩階段提交併進行擴展。算法中將節點分爲三種類型:

•倡議者proposer:提交一個提案,等待大家批准爲結案,往往是客戶端擔任。•接受者acceptor:負責對提案進行投票,往往服務器擔任。提議超過半數的接受者投票及被選中。•學習者learner:被告知提案結果,並與之統一,不參與投票過程。客戶端和服務端都可擔任。

每個節點在協議中可以擔任多個角色。

Paxos的特點:

•一個或多個節點可以提出提議。•系統針對所有提案中的某個提案必須達成一致。•最多隻能對一個確定的提案達成一致。•只要超過半數的節點存活且可互相通信,整個系統一定能達成一致狀態。

第一階段A

Proposer選擇一個提議編號n,向所有的Acceptor廣播Prepare(n)請求。

第一階段B

Acceptor接收到Prepare(n)請求,若提議編號n比之前接收的Prepare請求都要大,則承諾將不會接收提議編號比n小的提議,並且帶上之前Accept的提議中編號小於n的最大的提議,否則不予理會。

第二階段A

Proposer得到了多數Acceptor的承諾後,如果沒有發現有一個Acceptor接受過一個值,那麼向所有的Acceptor發起自己的值和提議編號n,否則,從所有接受過的值中選擇對應的提議編號最大的,作爲提議的值,提議編號仍然爲n。

第二階段B

Acceptor接收到提議後,如果該提議編號不違反自己做過的承諾,則接受該提議。

Paxos 例子說明

樓主這個例子來自中文維基百科,但樓主爲了形象化,輔以圖片解釋,但願不會讓人更迷糊。

例子:

在 Paxos 島上,有A1, A2, A3, A4, A5 5位議員,就稅率問題進行決議。我們假設幾個場景來解釋:

場景 1:

假設 A1 說:稅率應該是 10%。而此時只有他一個人提這個建議。如下圖:

很完美,沒有任何人和他競爭提案,他的這個提案毫無阻撓的通過了。A2 - A5 都會迴應他:我們收到了你的提案,等待最終的批准。而 A1 在收到 2 份回覆後,就可以發佈最終的決議:稅率定位 10%,不用再討論了。

這裏有個注意的地方就是:爲什麼收到了 2 份回覆就可以確定提案了呢?答:因爲包括他自己,就達到 3 個人了,少數服從多數。如果各位聽說過鴿籠原理/抽屜原理,就明白個大概了。有人說,鴿籠原理/抽屜原理就是 Paxos 的核心思想。

場景 2:

現在我們假設在 A1 提出 10% 稅率提案的同時, A5 決定將稅率定爲 20%,如果這個提案要通過侍從送到其他議員的案頭,A1 的草案將由 4 位侍從送到 A2-A5 那裏。但是侍從不靠譜(代表分佈式環境不靠譜),負責 A2 和 A3 的侍從順利送達,而負責 A4 和 A5 的侍從則開溜了!

而 A5 的草案則送到了 A4 和 A3 的手中。

現在,A1 ,A2,A3 收到了 A1 的提案,A3,A4, A5 收到 A5 的提案,按照 Paxos 的協議,A1,A2,A4,A5 4個侍從將接受他們的提案,侍從拿着回覆:我已收到你的提案,等待最終批准 回到提案者那裏。

而 A3 的行爲將決定批准哪一個。

當 A3 同時收到了 A1 和 A5 的請求,該如何抉擇呢?不同的抉擇將會導致不同的結果。

有 3 種情況,我們分析一下:

場景2:情況一

假設 A1 的提案先送到 A3 那裏,並且 A3 接受了該提案並回復了侍從。這樣,A1 加上 A2 加上 A3,構成了多數派,成功確定了稅率爲 10%。而 A5 的侍從由於路上喝酒喝多了,晚到了一天,等他到了,稅率已經確定了,A3 回覆 A5:兄弟,你來的太晚了,稅率已經定好了,不用折騰了,聽 A1 的吧。

如下圖:

場景2:情況二

依然假設 A1 的提案先送到 A3 處,但是這次 A5 的侍從不是放假了,只是中途耽擱了一會。這次, A3 依然會將"接受"回覆給 A1 .但是在決議成型之前它又收到了 A5 的提案。這時協議根據 A5 的身份地位有兩種處理方式,但結果相同。

•當 A5 地位很高,例如 CEO,就回復 A5:我已收到您的提案,等待最終批准,但是您之前有人提出將稅率定爲10%,請明察。•當 A5 沒地位,普通碼農一個,直接不回覆。等待 A1 廣播:稅率定爲 10% 啦!!!

如下圖:

場景2:情況三

在這個情況中,我們將看見,根據提案的時間及提案者的權勢決定是否應答是有意義的。在這裏,時間和提案者的權勢就構成了給提案編號的依據。這樣的編號符合"任何兩個提案之間構成偏序"的要求。

A1 和 A5 同樣提出上述提案,這時 A1 可以正常聯繫 A2 和 A3,A5 也可以正常聯繫這兩個人。這次 A2 先收到 A1 的提案; A3 則先收到 A5 的提案。而 A5 更有地位。

在這種情況下,已經回答 A1 的 A2 發現有比 A1 更有權勢的 A5 提出了稅率 20% 的新提案,於是回覆A5說:我已收到您的提案,等待最終批准。

而回復 A5 的 A3 發現新的提案者A1是個小人物,沒地位不予應答。

此時,A5 得到了 A2,A3 的回覆,於是 A5 說:稅率定爲 20%,別再討論了。

那 A4 呢?A4 由於睡過頭了,迷迷糊糊的說:現有的稅率是什麼? 如果沒有決定,則建議將其定爲 15%.

這個時候,其他的議員就告訴他:哥們,已經定爲 20% 了,別折騰了。洗洗繼續睡吧。

整個過程如下圖:

Paxos的死鎖情況

“活鎖”的根本原因在於兩個proposer交替提案,避免“活鎖”的方式爲,如果一個proposer通過accpter返回的消息知道此時有更高編號的提案被提出時,該proposer靜默一段時間,而不是馬上提出更高的方案,靜默期長短爲一個提案從提出到被接受的大概時間長度即可,靜默期過後,proposer重新提案。系統中之所以要有主proposer的原因在於,如果每次數據更改都用paxos,那實在是太慢了,還是通過主節點下發請求這樣來的快,因爲省去了不必要的paxos時間。所以選擇主proposer用paxos算法,因爲選主的頻率要比更改數據頻率低太多。但是主proposer掛了咋整,整個集羣就一直處於不可用狀態,所以一般都用租約的方式,如果proposer掛了,則租約會過期,其它proposer就可以再重新選主,如果不掛,則主proposer自己續租。

小結:

Paxos協議最終解決什麼問題?

當一個提議被多數派接受後,這個提議對應的值被Chosen(選定),一旦有一個值被Chosen,那麼只要按照協議的規則繼續交互,後續被Chosen的值都是同一個值,也就是這個Chosen值的一致性問題。

Paxos 的目標:保證最終有一個提案會被選定,當提案被選定後,其他議員最終也能獲取到被選定的提案。

Paxos 協議用來解決的問題可以用一句話來簡化:將所有節點都寫入同一個值,且被寫入後不再更改。

Raft一致性算法

Raft算法是Paxos算法的一種簡化實現。

包括三種角色:leader,candidate和follower。

•follow:所有節點都以follower的狀態開始,如果沒有收到leader消息則會變成candidate狀態。•candidate:會向其他節點拉選票,如果得到大部分的票則成爲leader,這個過程是Leader選舉。•leader:所有對系統的修改都會先經過leader。

其有兩個基本過程:

•Leader選舉:每個candidate隨機經過一定時間都會提出選舉方案,最近階段中的票最多者被選爲leader。•同步log:leader會找到系統中log(各種事件的發生記錄)最新的記錄,並強制所有的follow來刷新到這個記錄。

Raft一致性算法是通過選出一個leader來簡化日誌副本的管理,例如日誌項(log entry)只允許從leader流向follower。

下面是動畫演示Raft,清晰理解Raft共識如何達成。

http://thesecretlivesofdata.com/raft/

1.針對簡化版拜占庭將軍問題,Raft 解決方案

假設將軍中沒有叛軍,信使的信息可靠但有可能被暗殺的情況下,將軍們如何達成一致性決定?

Raft 的解決方案大概可以理解成 先在所有將軍中選出一個大將軍,所有的決定由大將軍來做。選舉環節:比如說現在一共有3個將軍 A, B, C,每個將軍都有一個隨機時間的倒計時器,倒計時一結束,這個將軍就會把自己當成大將軍候選人,然後派信使去問其他幾個將軍,能不能選我爲總將軍?假設現在將軍A倒計時結束了,他派信使傳遞選舉投票的信息給將軍B和C,如果將軍B和C還沒把自己當成候選人(倒計時還沒有結束),並且沒有把選舉票投給其他,他們把票投給將軍A,信使在回到將軍A時,將軍A知道自己收到了足夠的票數,成爲了大將軍。在這之後,是否要進攻就由大將軍決定,然後派信使去通知另外兩個將軍,如果在一段時間後還沒有收到回覆(可能信使被暗殺),那就再重派一個信使,直到收到回覆。

1.選主 Leader Election

2.1 正常情況下選主

假設現在有如圖5個節點,5個節點一開始的狀態都是 Follower。

在一個節點倒計時結束 (Timeout) 後,這個節點的狀態變成 Candidate 開始選舉,它給其他幾個節點發送選舉請求 (RequestVote)

其他四個節點都返回成功,這個節點的狀態由 Candidate 變成了 Leader,並在每個一小段時間後,就給所有的 Follower 發送一個 Heartbeat 以保持所有節點的狀態,Follower 收到 Leader 的 Heartbeat 後重設 Timeout。

這是最簡單的選主情況,只要有超過一半的節點投支持票了,Candidate 纔會被選舉爲 Leader,5個節點的情況下,3個節點 (包括 Candidate 本身) 投了支持就行。

2.2 Leader 出故障情況下的選主

一開始已經有一個 Leader,所有節點正常運行。

Leader 出故障掛掉了,其他四個 Follower 將進行重新選主。

4個節點的選主過程和5個節點的類似,在選出一個新的 Leader 後,原來的 Leader 恢復了又重新加入了,這個時候怎麼處理?在 Raft 裏,第幾輪選舉是有記錄的,重新加入的 Leader 是第一輪選舉 (Term 1) 選出來的,而現在的 Leader 則是 Term 2,所有原來的 Leader 會自覺降級爲 Follower

2.3 多個 Candidate 情況下的選主

假設一開始有4個節點,都還是 Follower。

有兩個 Follower 同時 Timeout,都變成了 Candidate 開始選舉,分別給一個 Follower 發送了投票請求。

兩個 Follower 分別返回了ok,這時兩個 Candidate 都只有2票,要3票才能被選成 Leader。

兩個 Candidate 會分別給另外一個還沒有給自己投票的 Follower 發送投票請求。

但是因爲 Follower 在這一輪選舉中,都已經投完票了,所以都拒絕了他們的請求。所以在 Term 2 沒有 Leader 被選出來。

這時,兩個節點的狀態是 Candidate,兩個是 Follower,但是他們的倒計時器仍然在運行,最先 Timeout 的那個節點會進行發起新一輪 Term 3 的投票。

兩個 Follower 在 Term 3 還沒投過票,所以返回 OK,這時 Candidate 一共有三票,被選爲了 Leader。

如果 Leader Heartbeat 的時間晚於另外一個 Candidate timeout 的時間,另外一個 Candidate 仍然會發送選舉請求。

兩個 Follower 已經投完票了,拒絕了這個 Candidate 的投票請求。

Leader 進行 Heartbeat, Candidate 收到後狀態自動轉爲 Follower,完成選主。

以上是 Raft 最重要活動之一選主的介紹,以及在不同情況下如何進行選主。

3. 複製日誌 Log Replication

3.1 正常情況下複製日誌

Raft 在實際應用場景中的一致性更多的是體現在不同節點之間的數據一致性,客戶端發送請求到任何一個節點都能收到一致的返回,當一個節點出故障後,其他節點仍然能以已有的數據正常進行。在選主之後的複製日誌就是爲了達到這個目的。

一開始,Leader 和 兩個 Follower 都沒有任何數據。

客戶端發送請求給 Leader,儲存數據 “sally”,Leader 先將數據寫在本地日誌,這時候數據還是 Uncommitted (還沒最終確認,紅色表示)

Leader 給兩個 Follower 發送 AppendEntries 請求,數據在 Follower 上沒有衝突,則將數據暫時寫在本地日誌,Follower 的數據也還是 Uncommitted。

Follower 將數據寫到本地後,返回 OK。Leader 收到後成功返回,只要收到的成功的返回數量超過半數 (包含Leader),Leader 將數據 “sally” 的狀態改成 Committed。( 這個時候 Leader 就可以返回給客戶端了)

Leader 再次給 Follower 發送 AppendEntries 請求,收到請求後,Follower 將本地日誌裏 Uncommitted 數據改成 Committed。這樣就完成了一整個複製日誌的過程,三個節點的數據是一致的,

3.2 Network Partition 情況下進行復制日誌

在 Network Partition 的情況下,部分節點之間沒辦法互相通信,Raft 也能保證在這種情況下數據的一致性。

一開始有 5 個節點處於同一網絡狀態下。

Network Partition 將節點分成兩邊,一邊有兩個節點,一邊三個節點。

兩個節點這邊已經有 Leader 了,來自客戶端的數據 “bob” 通過 Leader 同步到 Follower。

因爲只有兩個節點,少於3個節點,所以 “bob” 的狀態仍是 Uncommitted。所以在這裏,服務器會返回錯誤給客戶端

另外一個 Partition 有三個節點,進行重新選主。客戶端數據 “tom” 發到新的 Leader,通過和上節網絡狀態下相似的過程,同步到另外兩個 Follower。

因爲這個 Partition 有3個節點,超過半數,所以數據 “tom” 都 Commit 了。

網絡狀態恢復,5個節點再次處於同一個網絡狀態下。但是這裏出現了數據衝突 “bob" 和 “tom"

三個節點的 Leader 廣播 AppendEntries

兩個節點 Partition 的 Leader 自動降級爲 Follower,因爲這個 Partition 的數據 “bob” 沒有 Commit,返回給客戶端的是錯誤,客戶端知道請求沒有成功,所以 Follower 在收到 AppendEntries 請求時,可以把 “bob“ 刪除,然後同步 ”tom”,通過這麼一個過程,就完成了在 Network Partition 情況下的複製日誌,保證了數據的一致性。

小結

Raft 是能夠實現分佈式系統強一致性的算法,每個系統節點有三種狀態 Follower,Candidate,Leader。實現 Raft 算法兩個最重要的事是:選主和複製日誌。

一致性協議之 ZAB

什麼是 ZAB 協議?ZAB 協議介紹

ZAB 協議全稱:Zookeeper Atomic Broadcast(Zookeeper 原子廣播協議)。

ZAB 協議是爲分佈式協調服務 Zookeeper 專門設計的一種支持 崩潰恢復 和 原子廣播 協議。

整個 Zookeeper 就是在這兩個模式之間切換。簡而言之,當 Leader 服務可以正常使用,就進入消息廣播模式,當 Leader 不可用時,則進入崩潰恢復模式。

基於該協議,Zookeeper 實現了一種 主備模式 的系統架構來保持集羣中各個副本之間數據一致性。其中所有客戶端寫入數據都是寫入到 主進程(稱爲 Leader)中,然後,由 Leader 複製到備份進程(稱爲 Follower)中。【涉及到2PC單點問題的解決,崩潰恢復】

選擇機制中的概念

1、Serverid:服務器ID

比如有三臺服務器,編號分別是1,2,3。

編號越大在選擇算法中的權重越大。

2、Zxid:數據ID

服務器中存放的最大數據ID。【zxid實際上是一個64位的數字,高32位是epoch(時期; 紀元; 世; 新時代)用來標識leader是否發生改變,如果有新的leader產生出來,epoch會自增,低32位用來遞增計數。】

值越大說明數據越新,在選舉算法中數據越新權重越大。

3、Epoch:邏輯時鐘

或者叫投票的次數,同一輪投票過程中的邏輯時鐘值是相同的。每投完一次票這個數據就會增加,然後與接收到的其它服務器返回的投票信息中的數值相比,根據不同的值做出不同的判斷。

4、Server狀態:選舉狀態

LOOKING,競選狀態。

FOLLOWING,隨從狀態,同步leader狀態,參與投票。

OBSERVING,觀察狀態,同步leader狀態,不參與投票。

LEADING,領導者狀態。

選舉消息內容

在投票完成後,需要將投票信息發送給集羣中的所有服務器,它包含如下內容:服務器ID、數據ID、邏輯時鐘、選舉狀態。

zookeeper是如何保證事務的順序一致性的(保證消息有序) 在整個消息廣播中,Leader會將每一個事務請求轉換成對應的 proposal 來進行廣播,並且在廣播 事務Proposal 之前,Leader服務器會首先爲這個事務Proposal分配一個全局單遞增的唯一ID,稱之爲事務ID(即zxid),由於Zab協議需要保證每一個消息的嚴格的順序關係,因此必須將每一個proposal按照其zxid的先後順序進行排序和處理。

消息廣播

1)在zookeeper集羣中,數據副本的傳遞策略就是採用消息廣播模式。zookeeper中農數據副本的同步方式與二段提交相似,但是卻又不同。二段提交要求協調者必須等到所有的參與者全部反饋ACK確認消息後,再發送commit消息。要求所有的參與者要麼全部成功,要麼全部失敗。二段提交會產生嚴重的阻塞問題。

2)Zab協議中 Leader 等待 Follower 的ACK反饋消息是指“只要半數以上的Follower成功反饋即可,不需要收到全部Follower反饋”。

消息廣播具體步驟

1)客戶端發起一個寫操作請求。

2)Leader 服務器將客戶端的請求轉化爲事務 Proposal 提案,同時爲每個 Proposal 分配一個全局的ID,即zxid。

3)Leader 服務器爲每個 Follower 服務器分配一個單獨的隊列,然後將需要廣播的 Proposal 依次放到隊列中取,並且根據 FIFO 策略進行消息發送。

4)Follower 接收到 Proposal 後,會首先將其以事務日誌的方式寫入本地磁盤中,寫入成功後向 Leader 反饋一個 Ack 響應消息。

5)Leader 接收到超過半數以上 Follower 的 Ack 響應消息後,即認爲消息發送成功,可以發送 commit 消息。

6)Leader 向所有 Follower 廣播 commit 消息,同時自身也會完成事務提交。Follower 接收到 commit 消息後,會將上一條事務提交。

zookeeper 採用 Zab 協議的核心,就是隻要有一臺服務器提交了 Proposal,就要確保所有的服務器最終都能正確提交 Proposal。這也是 CAP/BASE 實現最終一致性的一個體現。

Leader 服務器與每一個 Follower 服務器之間都維護了一個單獨的 FIFO 消息隊列進行收發消息,使用隊列消息可以做到異步解耦。Leader 和 Follower 之間只需要往隊列中發消息即可。如果使用同步的方式會引起阻塞,性能要下降很多。

崩潰恢復

崩潰恢復主要包括兩部分:Leader選舉 和 數據恢復

zookeeper是如何選取主leader的?

當leader崩潰或者leader失去大多數的follower,這時zk進入恢復模式,恢復模式需要重新選舉出一個新的leader,讓所有的Server都恢復到一個正確的狀態。

Zookeeper選主流程 選舉流程詳述

一、首先開始選舉階段,每個Server讀取自身的zxid。

二、發送投票信息

a、首先,每個Server第一輪都會投票給自己。

b、投票信息包含 :所選舉leader的Serverid,Zxid,Epoch。Epoch會隨着選舉輪數的增加而遞增。

三、接收投票信息

1、如果服務器B接收到服務器A的數據(服務器A處於選舉狀態(LOOKING 狀態)

1)首先,判斷邏輯時鐘值:

a)如果發送過來的邏輯時鐘Epoch大於目前的邏輯時鐘。首先,更新本邏輯時鐘Epoch,同時清空本輪邏輯時鐘收集到的來自其他server的選舉數據。然後,判斷是否需要更新當前自己的選舉leader Serverid。判斷規則rules judging:保存的zxid最大值和leader Serverid來進行判斷的。先看數據zxid,數據zxid大者勝出;其次再判斷leader Serverid,leader Serverid大者勝出;然後再將自身最新的選舉結果(也就是上面提到的三種數據(leader Serverid,Zxid,Epoch)廣播給其他server)

b)如果發送過來的邏輯時鐘Epoch小於目前的邏輯時鐘。說明對方server在一個相對較早的Epoch中,這裏只需要將本機的三種數據(leader Serverid,Zxid,Epoch)發送過去就行。

c)如果發送過來的邏輯時鐘Epoch等於目前的邏輯時鐘。再根據上述判斷規則rules judging來選舉leader ,然後再將自身最新的選舉結果(也就是上面提到的三種數據(leader Serverid,Zxid,Epoch)廣播給其他server)。

2)其次,判斷服務器是不是已經收集到了所有服務器的選舉狀態:若是,根據選舉結果設置自己的角色(FOLLOWING還是LEADER),退出選舉過程就是了。

最後,若沒有收集到所有服務器的選舉狀態:也可以判斷一下根據以上過程之後最新的選舉leader是不是得到了超過半數以上服務器的支持,如果是,那麼嘗試在200ms內接收一下數據,如果沒有新的數據到來,說明大家都已經默認了這個結果,同樣也設置角色退出選舉過程。

2、 如果所接收服務器A處在其它狀態(FOLLOWING或者LEADING)。

a)邏輯時鐘Epoch等於目前的邏輯時鐘,將該數據保存到recvset。此時Server已經處於LEADING狀態,說明此時這個server已經投票選出結果。若此時這個接收服務器宣稱自己是leader, 那麼將判斷是不是有半數以上的服務器選舉它,如果是則設置選舉狀態退出選舉過程。

b) 否則這是一條與當前邏輯時鐘不符合的消息,那麼說明在另一個選舉過程中已經有了選舉結果,於是將該選舉結果加入到outofelection集合中,再根據outofelection來判斷是否可以結束選舉,如果可以也是保存邏輯時鐘,設置選舉狀態,退出選舉過程。【recvset:用來記錄選票信息,以方便後續統計;outofelection:用來記錄選舉邏輯之外的選票,例如當一個服務器加入zookeeper集羣時,因爲集羣已經存在,不用重新選舉,只需要在滿足一定條件下加入集羣即可。】

描述Leader選擇過程中的狀態變化,這是假設全部實例中均沒有數據,假設服務器啓動順序分別爲:A,B,C。

Zab 協議如何保證數據一致性

假設兩種異常情況:1、一個事務在 Leader 上提交了,並且過半的 Folower 都響應 Ack 了,但是 Leader 在 Commit 消息發出之前掛了。2、假設一個事務在 Leader 提出之後,Leader 掛了。

要確保如果發生上述兩種情況,數據還能保持一致性,那麼 Zab 協議選舉算法必須滿足以下要求:

Zab 協議崩潰恢復要求滿足以下兩個要求:1)確保已經被 Leader 提交的 Proposal 必須最終被所有的 Follower 服務器提交。2)確保丟棄已經被 Leader 提出的但是沒有被提交的 Proposal。

根據上述要求 Zab協議需要保證選舉出來的Leader需要滿足以下條件:1)新選舉出來的 Leader 不能包含未提交的 Proposal 。即新選舉的 Leader 必須都是已經提交了 Proposal 的 Follower 服務器節點。2)新選舉的 Leader 節點中含有最大的 zxid 。這樣做的好處是可以避免 Leader 服務器檢查 Proposal 的提交和丟棄工作。

Zab 如何數據同步

1)完成 Leader 選舉後(新的 Leader 具有最高的zxid),在正式開始工作之前(接收事務請求,然後提出新的 Proposal),Leader 服務器會首先確認事務日誌中的所有的 Proposal 是否已經被集羣中過半的服務器 Commit。

2)Leader 服務器需要確保所有的 Follower 服務器能夠接收到每一條事務的 Proposal ,並且能將所有已經提交的事務 Proposal 應用到內存數據中。等到 Follower 將所有尚未同步的事務 Proposal 都從 Leader 服務器上同步過啦並且應用到內存數據中以後,Leader 纔會把該 Follower 加入到真正可用的 Follower 列表中。

Zab 數據同步過程中,如何處理需要丟棄的 Proposal

在 Zab 的事務編號 zxid 設計中,zxid是一個64位的數字。

其中低32位可以看成一個簡單的單增計數器,針對客戶端每一個事務請求,Leader 在產生新的 Proposal 事務時,都會對該計數器加1。而高32位則代表了 Leader 週期的 epoch 編號。

epoch 編號可以理解爲當前集羣所處的年代,或者週期。每次Leader變更之後都會在 epoch 的基礎上加1,這樣舊的 Leader 崩潰恢復之後,其他Follower 也不會聽它的了,因爲 Follower 只服從epoch最高的 Leader 命令。

每當選舉產生一個新的 Leader ,就會從這個 Leader 服務器上取出本地事務日誌充最大編號 Proposal 的 zxid,並從 zxid 中解析得到對應的 epoch 編號,然後再對其加1,之後該編號就作爲新的 epoch 值,並將低32位數字歸零,由0開始重新生成zxid。

Zab 協議通過 epoch 編號來區分 Leader 變化週期,能夠有效避免不同的 Leader 錯誤的使用了相同的 zxid 編號提出了不一樣的 Proposal 的異常情況。

基於以上策略:

當一個包含了上一個 Leader 週期中尚未提交過的事務 Proposal 的服務器啓動時,當這臺機器加入集羣中,以 Follower 角色連上 Leader 服務器後,Leader 服務器會根據自己服務器上最後提交的 Proposal 來和 Follower 服務器的 Proposal 進行比對,比對的結果肯定是 Leader 要求 Follower 進行一個回退操作,回退到一個確實已經被集羣中過半機器 Commit 的最新 Proposal。

小結

ZAB 協議和我們之前看的 Raft 協議實際上是有相似之處的,比如都有一個 Leader,用來保證一致性(Paxos 並沒有使用 Leader 機制保證一致性)。再有采取過半即成功的機制保證服務可用(實際上 Paxos 和 Raft 都是這麼做的)。

ZAB 讓整個 Zookeeper 集羣在兩個模式之間轉換,消息廣播和崩潰恢復,消息廣播可以說是一個簡化版本的 2PC,通過崩潰恢復解決了 2PC 的單點問題,通過隊列解決了 2PC 的同步阻塞問題。

而支持崩潰恢復後數據準確性的就是數據同步了,數據同步基於事務的 ZXID 的唯一性來保證。通過 + 1 操作可以辨別事務的先後順序。

NWR模型

Amazon Dynamo的NWR模型。NWR模型把CAP的選擇權交給了用戶,讓用戶自己的選擇你的CAP中的哪兩個。

所謂NWR模型。N代表N個備份,W代表要寫入至少W份才認爲成功,R表示至少讀取R個備份。配置的時候要求W+R > N。因爲W+R > N, 所以 R > N-W 這個是什麼意思呢?就是讀取的份數一定要比總備份數減去確保寫成功的倍數的差值要大。

也就是說,每次讀取,都至少讀取到一個最新的版本。從而不會讀到一份舊數據。當我們需要高可寫的環境的時候,我們可以配置W = 1 如果N=3 那麼R = 3。這個時候只要寫任何節點成功就認爲成功,但是讀的時候必須從所有的節點都讀出數據。如果我們要求讀的高效率,我們可以配置 W=N R=1。這個時候任何一個節點讀成功就認爲成功,但是寫的時候必須寫所有三個節點成功才認爲成功。

NWR模型的一些設置會造成髒數據的問題,因爲這很明顯不是像Paxos一樣是一個強一致的東西,所以,可能每次的讀寫操作都不在同一個結點上,於是會出現一些結點上的數據並不是最新版本,但卻進行了最新的操作。

所以,Amazon Dynamo引了數據版本的設計。也就是說,如果你讀出來數據的版本是v1,當你計算完成後要回填數據後,卻發現數據的版本號已經被人更新成了v2,那麼服務器就會拒絕你。版本這個事就像“樂觀鎖”一樣。

但是,對於分佈式和NWR模型來說,版本也會有惡夢的時候——就是版本衝的問題,比如:我們設置了N=3 W=1,如果A結點上接受了一個值,版本由v1 -> v2,但還沒有來得及同步到結點B上(異步的,應該W=1,寫一份就算成功),B結點上還是v1版本,此時,B結點接到寫請求,按道理來說,他需要拒絕掉,但是他一方面並不知道別的結點已經被更新到v2,另一方面他也無法拒絕,因爲W=1,所以寫一分就成功了。於是,出現了嚴重的版本衝突。

Amazon的Dynamo把版本衝突這個問題巧妙地迴避掉了——版本衝突這個事交給用戶自己來處理。

於是,Dynamo引入了Vector Clock(矢量鍾)這個設計。這個設計讓每個結點各自記錄自己的版本信息,也就是說,對於同一個數據,需要記錄兩個事:1)誰更新的我,2)我的版本號是什麼。

下面,我們來看一個操作序列:

1)一個寫請求,第一次被節點A處理了。節點A會增加一個版本信息(A,1)。我們把這個時候的數據記做D1(A,1)。然後另外一個對同樣key的請求還是被A處理了於是有D2(A,2)。這個時候,D2是可以覆蓋D1的,不會有衝突產生。

2)現在我們假設D2傳播到了所有節點(B和C),B和C收到的數據不是從客戶產生的,而是別人複製給他們的,所以他們不產生新的版本信息,所以現在B和C所持有的數據還是D2(A,2)。於是A,B,C上的數據及其版本號都是一樣的。

3)如果我們有一個新的寫請求到了B結點上,於是B結點生成數據D3(A,2; B,1),意思是:數據D全局版本號爲3,A升了兩新,B升了一次。這不就是所謂的代碼版本的log麼?

4)如果D3沒有傳播到C的時候又一個請求被C處理了,於是,以C結點上的數據是D4(A,2; C,1)。

5)好,最精彩的事情來了:如果這個時候來了一個讀請求,我們要記得,我們的W=1 那麼R=N=3,所以R會從所有三個節點上讀,此時,他會讀到三個版本:

•A結點:D2(A,2)•B結點:D3(A,2; B,1);•C結點:D4(A,2; C,1)

6)這個時候可以判斷出,D2已經是舊版本(已經包含在D3/D4中),可以捨棄。

7)但是D3和D4是明顯的版本衝突。於是,交給調用方自己去做版本衝突處理。就像源代碼版本管理一樣。

很明顯,上述的Dynamo的配置用的是CAP裏的A和P。

乾貨分享

最近將個人學習筆記整理成冊,使用PDF分享。關注我,回覆如下代碼,即可獲得百度盤地址,無套路領取!

001:《Java併發與高併發解決方案》學習筆記;002:《深入JVM內核——原理、診斷與優化》學習筆記;003:《Java面試寶典》004:《Docker開源書》005:《Kubernetes開源書》006:《DDD速成(領域驅動設計速成)》007:全部008:加技術羣討論

近期熱文

LinkedBlockingQueue vs ConcurrentLinkedQueue解讀Java 8 中爲併發而生的 ConcurrentHashMapRedis性能監控指標彙總最全的DevOps工具集合,再也不怕選型了!微服務架構下,解決數據庫跨庫查詢的一些思路聊聊大廠面試官必問的 MySQL 鎖機制

關注我

喜歡就點個"在看"唄^_^

本文分享自微信公衆號 - IT牧場(itmuch_com)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章