Raft對比ZAB協議

系列文章git

0 一致性問題

本篇文章想總結下Raft和ZAB在處理一些一致性問題上的作法,詳見以前對這2個算法的描述github

上述分別是針對以下算法實現的討論:算法

  • Raft的實現copycat,因爲Raft算法自己已經介紹的至關清晰,copycat基本上和Raft算法保持一致
  • ZAB的實現ZooKeeper,因爲ZooKeeper裏面的不少實現細節並無在ZAB裏體現(ZAB裏面只是一個大概,沒有像Raft那麼具體),因此這裏討論的都是ZooKeeper的實現

一致性算法在實現狀態機這種應用時,有哪些常見的問題:微信

  • 1 leader選舉學習

    • 1.1 通常的leader選舉過程atom

      選舉的輪次.net

      選舉出來的leader要包含更多的日誌設計

    • 1.2 leader選舉的效率日誌

      會不會出現split vote?以及各自的特色是?server

    • 1.3 加入一個已經完成選舉的集羣

      怎麼發現已完成選舉的leader?

      加入過程是否對leader處理請求的過程形成阻塞?

    • 1.4 leader選舉的觸發

      誰在負責檢測須要進入leader選舉?

  • 2 上一輪次的leader

    • 2.1 上一輪次的leader的殘留的數據怎麼處理?
    • 2.2 怎麼阻止以前的leader假死的問題
  • 3 請求處理流程

    • 3.1 請求處理的通常流程

    • 3.2 日誌的連續性問題

    • 3.3 如何保證順序

      • 3.3.1 正常同步過程的順序

      • 3.3.2 異常過程的順序

        follower掛掉又鏈接

        leader更換

    • 3.4 請求處理流程的異常

  • 4 分區的處理

下面分別來看看Raft和ZooKeeper怎麼來解決的

1 leader選舉

爲何要進行leader選舉?

在實現一致性的方案,能夠像base-paxos那樣不須要leader選舉,這種方案達成一件事情的一致性還好,面對多件事情的一致性就比較複雜了,因此經過選舉出一個leader來簡化實現的複雜性。

1.1 通常的leader選舉過程

更多的有2個要素:

  • 1.1.1 選舉輪次
  • 1.1.2 leader包含更多的日誌

1.1.1 選舉投票可能會屢次輪番上演,爲了區分,因此須要定義你的投票是屬於哪一個輪次的。

  • Raft定義了term來表示選舉輪次
  • ZooKeeper定義了electionEpoch來表示

他們都須要在某個輪次內達成過半投票來結束選舉過程

1.1.2 投票PK的過程,更多的是日誌越新越多者獲勝

在選舉leader的時候,一般都但願

選舉出來的leader至少包含以前所有已提交的日誌

天然想到包含的日誌越新越大那就越好。

一般都是比較最後一個日誌記錄,如何來定義最後一個日誌記錄?

有2種選擇,一種就是全部日誌中的最後一個日誌,另外一種就是全部已提交中的最後一個日誌。目前Raft和ZooKeeper都是採用前一種方式。日誌的越新越大表示:輪次新的優先,而後纔是同一輪次下日誌記錄大的優先

  • Raft:term大的優先,而後entry的index大的優先

  • ZooKeeper:peerEpoch大的優先,而後zxid大的優先

    ZooKeeper有2個輪次,一個是選舉輪次electionEpoch,另外一個是日誌的輪次peerEpoch(即表示這個日誌是哪一個輪次產生的)。而Raft則是隻有一個輪次,至關於日誌輪次和選舉輪次共用了。至於ZooKeeper爲何要把這2個輪次分開,這個稍後再細究,有興趣的能夠一塊兒研究。

可是有一個問題就是:經過上述的日誌越新越大的比較方式能達到咱們的上述但願嗎?

特殊狀況下是不能的,這個特殊狀況詳細見上述給出Raft算法賞析的這一部分

不能直接提交以前term的entries的案例

這個案例就是這種比較方式會選舉出來的leader可能並不包含已經提交的日誌,而Raft的作法則是對於日誌的提交多加一個限制條件,即不能直接提交以前term的已過半的entry,即把這一部分的日誌限制成未提交的日誌,從而來實現上述的但願。

ZooKeeper呢?會不會出現這種狀況?又是如何處理的?

ZooKeeper是不會出現這種狀況的,由於ZooKeeper在每次leader選舉完成以後,都會進行數據之間的同步糾正,因此每個輪次,你們都日誌內容都是統一的

而Raft在leader選舉完成以後沒有這個同步過程,而是靠以後的AppendEntries RPC請求的一致性檢查來實現糾正過程,則就會出現上述案例中隔了幾個輪次還不統一的現象

1.2 leader選舉的效率

Raft中的每一個server在某個term輪次內只能投一次票,哪一個candidate先請求投票誰就可能先得到投票,這樣就可能形成split vote,即各個candidate都沒有收到過半的投票,Raft經過candidate設置不一樣的超時時間,來快速解決這個問題,使得先超時的candidate(在其餘人還未超時時)優先請求來得到過半投票

ZooKeeper中的每一個server,在某個electionEpoch輪次內,能夠投屢次票,只要遇到更大的票就更新,而後分發新的投票給全部人。這種狀況下不存在split vote現象,同時有利於選出含有更新更多的日誌的server,可是選舉時間理論上相對Raft要花費的多。

1.3 加入一個已經完成選舉的集羣

  • 1.3.1 怎麼發現已完成選舉的leader?
  • 1.3.2 加入過程是否阻塞整個請求?

1.3.1 怎麼發現已完成選舉的leader?

一個server啓動後(該server原本就屬於該集羣的成員配置之一,因此這裏不是新加機器),如何加入一個已經選舉完成的集羣

  • Raft:比較簡單,該server啓動後,會收到leader的AppendEntries RPC,這時就會從RPC中獲取leader信息,識別到leader,即便該leader是一個老的leader,以後新leader仍然會發送AppendEntries RPC,這時就會接收到新的leader了(由於新leader的term比老leader的term大,因此會更新leader)

  • ZooKeeper:該server啓動後,會向全部的server發送投票通知,這時候就會收處處於LOOKING、FOLLOWING狀態的server的投票(這種狀態下的投票指向的leader),則該server放棄本身的投票,判斷上述投票是否過半,過半則能夠確認該投票的內容就是新的leader。

1.3.2 加入過程是否阻塞整個請求?

這個其實還要看對日誌的設計是不是連續的

  • 若是是連續的,則leader中只須要保存每一個follower上一次的同步位置,這樣在同步的時候就會自動將以前欠缺的數據補上,不會阻塞整個請求過程

    目前Raft的日誌是依靠index來實現連續的

  • 若是不是連續的,則在確認follower和leader當前數據的差別的時候,是須要獲取leader當前數據的讀鎖,禁止這個期間對數據的修改。差別肯定完成以後,釋放讀鎖,容許leader數據被修改,每個修改記錄都要被保存起來,最後一一應用到新加入的follower中。

    目前ZooKeeper的日誌zxid並非嚴格連續的,容許有空洞

1.4 leader選舉的觸發

觸發通常有以下2個時機

  • server剛開始啓動的時候,觸發leader選舉

  • leader選舉完成以後,檢測到超時觸發,誰來檢測?

    • Raft:目前只是follower在檢測。follower有一個選舉時間,在該時間內若是未收到leader的心跳信息,則follower轉變成candidate,自增term發起新一輪的投票,leader遇到新的term則自動轉變成follower的狀態
    • ZooKeeper:leader和follower都有各自的檢測超時方式,leader是檢測是否過半follower心跳回復了,follower檢測leader是否發送心跳了。一旦leader檢測失敗,則leader進入LOOKING狀態,其餘follower過一段時間因收不到leader心跳也會進入LOOKING狀態,從而出發新的leader選舉。一旦follower檢測失敗了,則該follower進入LOOKING狀態,此時leader和其餘follower仍然保持良好,則該follower仍然是去學習上述leader的投票,而不是觸發新一輪的leader選舉

2 上一輪次的leader

2.1 上一輪次的leader的殘留的數據怎麼處理?

首先看下上一輪次的leader在掛或者失去leader位置以前,會有哪些數據?

  • 已過半複製的日誌
  • 未過半複製的日誌

一個日誌是否被過半複製,是否被提交,這些信息是由leader才能知曉的,那麼下一個leader該如何來斷定這些日誌呢?

下面分別來看看Raft和ZooKeeper的處理策略:

  • Raft:對於以前term的過半或未過半複製的日誌採起的是保守的策略,所有斷定爲未提交,只有噹噹前term的日誌過半了,纔會順便將以前term的日誌進行提交
  • ZooKeeper:採起激進的策略,對於全部過半仍是未過半的日誌都斷定爲提交,都將其應用到狀態機中

Raft的保守策略更可能是由於Raft在leader選舉完成以後,沒有同步更新過程來保持和leader一致(在能夠對外處理請求以前的這一同步過程)。而ZooKeeper是有該過程的

2.2 怎麼阻止上一輪次的leader假死的問題

這其實就和實現有密切的關係了。

  • Raft的copycat實現爲:每一個follower開通一個複製數據的RPC接口,誰均可以鏈接並調用該接口,因此Raft須要來阻止上一輪次的leader的調用。每一輪次都會有對應的輪次號,用來進行區分,Raft的輪次號就是term,一旦舊leader對follower發送請求,follower會發現當前請求term小於本身的term,則直接忽略掉該請求,天然就解決了舊leader的干擾問題

  • ZooKeeper:一旦server進入leader選舉狀態則該follower會關閉與leader之間的鏈接,因此舊leader就沒法發送複製數據的請求到新的follower了,也就沒法形成干擾了

3 請求處理流程

3.1 請求處理的通常流程

這個過程對比Raft和ZooKeeper基本上是一致的,大體過程都是過半複製

先來看下Raft:

  • client鏈接follower或者leader,若是鏈接的是follower則,follower會把client的請求(寫請求,讀請求則自身就能夠直接處理)轉發到leader
  • leader接收到client的請求,將該請求轉換成entry,寫入到本身的日誌中,獲得在日誌中的index,會將該entry發送給全部的follower(其實是批量的entries)
  • follower接收到leader的AppendEntries RPC請求以後,會將leader傳過來的批量entries寫入到文件中(一般並無當即刷新到磁盤),而後向leader回覆OK
  • leader收到過半的OK回覆以後,就認爲能夠提交了,而後應用到leader本身的狀態機中,leader更新commitIndex,應用完畢後回覆客戶端
  • 在下一次leader發給follower的心跳中,會將leader的commitIndex傳遞給follower,follower發現commitIndex更新了則也將commitIndex以前的日誌都進行提交和應用到狀態機中

再來看看ZooKeeper:

  • client鏈接follower或者leader,若是鏈接的是follower則,follower會把client的請求(寫請求,讀請求則自身就能夠直接處理)轉發到leader
  • leader接收到client的請求,將該請求轉換成一個議案,寫入到本身的日誌中,會將該議案發送給全部的follower(這裏只是單個發送)
  • follower接收到leader的議案請求以後,會將該議案寫入到文件中(一般並無當即刷新到磁盤),而後向leader回覆OK
  • leader收到過半的OK回覆以後,就認爲能夠提交了,leader會向全部的follower發送一個提交上述議案的請求,同時leader本身也會提交該議案,應用到本身的狀態機中,完畢後回覆客戶端
  • follower在接收到leader傳過來的提交議案請求以後,對該議案進行提交,應用到狀態機中

3.2 日誌的連續性問題

在須要保證順序性的前提下,在利用一致性算法實現狀態機的時候,究竟是實現連續性日誌好呢仍是實現非連續性日誌好呢?

  • 若是是連續性日誌,則leader在分發給各個follower的時候,只須要記錄每一個follower目前已經同步的index便可,如Raft
  • 若是是非連續性日誌,如ZooKeeper,則leader須要爲每一個follower單獨保存一個隊列,用於存放全部的改動,如ZooKeeper,一旦是隊列就引入了一個問題即順序性問題,即follower在和leader進行同步的時候,須要阻塞leader處理寫請求,先將follower和leader之間的差別數據先放入隊列,完成以後,解除阻塞,容許leader處理寫請求,即容許往該隊列中放入新的寫請求,從而來保證順序性

還有在複製和提交的時候:

  • 連續性日誌能夠批量進行
  • 非連續性日誌則只能一個一個來複制和提交

其餘有待後續補充

3.3 如何保證順序

具體順序是什麼?

這個就是先到達leader的請求,先被應用到狀態機。這就須要看正常運行過程、異常出現過程都是怎麼來保證順序的

3.3.1 正常同步過程的順序

  • Raft對請求先轉換成entry,複製時,也是按照leader中log的順序複製給follower的,對entry的提交是按index進行順序提交的,是能夠保證順序的
  • ZooKeeper在提交議案的時候也是按順序寫入各個follower對應在leader中的隊列,而後follower必然是按照順序來接收到議案的,對於議案的過半提交也都是一個個來進行的

3.3.2 異常過程的順序保證

如follower掛掉又重啓的過程:

  • Raft:重啓以後,因爲leader的AppendEntries RPC調用,識別到leader,leader仍然會按照leader的log進行順序複製,也不用關心在複製期間新的添加的日誌,在下一次同步中自動會同步

  • ZooKeeper:重啓以後,須要和當前leader數據之間進行差別的肯定,同時期間又有新的請求到來,因此須要暫時獲取leader數據的讀鎖,禁止此期間的數據更改,先將差別的數據先放入隊列,差別肯定完畢以後,還須要將leader中已提交的議案和未提交的議案也所有放入隊列,即ZooKeeper的以下2個集合數據

    • ConcurrentMap<Long, Proposal> outstandingProposals

      Leader擁有的屬性,每當提出一個議案,都會將該議案存放至outstandingProposals,一旦議案被過半認同了,就要提交該議案,則從outstandingProposals中刪除該議案

    • ConcurrentLinkedQueue<Proposal> toBeApplied

      Leader擁有的屬性,每當準備提交一個議案,就會將該議案存放至該列表中,一旦議案應用到ZooKeeper的內存樹中了,而後就能夠將該議案從toBeApplied中刪除

    而後再釋放讀鎖,容許leader進行處理寫數據的請求,該請求天然就添加在了上述隊列的後面,從而保證了隊列中的數據是有序的,從而保證發給follower的數據是有序的,follower也是一個個進行確認的,因此對於leader的回覆也是有序的

若是是leader掛了以後,從新選舉出leader,會不會有亂序的問題?

  • Raft:Raft對於以前term的entry被過半複製暫不提交,只有當本term的數據提交了才能將以前term的數據一塊兒提交,也是能保證順序的
  • ZooKeeper:ZooKeeper每次leader選舉以後都會進行數據同步,不會有亂序問題

3.4 請求處理流程的異常

一旦leader發給follower的數據出現超時等異常

  • Raft:會不斷重試,而且接口是冪等的
  • ZooKeeper:follower會斷開與leader之間的鏈接,從新加入該集羣,加入邏輯前面已經說了

4 分區的應對

目前ZooKeeper和Raft都是過半便可,因此對於分區是容忍的。如5臺機器,分區發生後分紅2部分,一部分3臺,另外一部分2臺,這2部分之間沒法相互通訊

其中,含有3臺的那部分,仍然能夠湊成一個過半,仍然能夠對外提供服務,可是它不容許有server再掛了,一旦再掛一臺則就所有不可用了。

含有2臺的那部分,則沒法提供服務,即只要鏈接的是這2臺機器,都沒法執行相關請求。

因此ZooKeeper和Raft在一旦分區發生的狀況下是是犧牲了高可用來保證一致性,即CAP理論中的CP。可是在沒有分區發生的狀況下既能保證高可用又能保證一致性,因此更想說的是所謂的CAP兩者取其一,並非說該系統一直保持CA或者CP或者AP,而是一個會變化的過程。在沒有分區出現的狀況下,既能夠保證C又能夠保證A,在分區出現的狀況下,那就須要從C和A中選擇同樣。ZooKeeper和Raft則都是選擇了C。

歡迎關注微信公衆號:乒乓狂魔

乒乓狂魔微信公衆號

相關文章
相關標籤/搜索