ZooKeeper自身數據一致性

@Author  : Spinach | GHB
@Link    : http://blog.csdn.net/bocai8058

0 前言

爲了高可用和數據安全起見,zk集羣一般都是由幾個節點構成(由n/2+1,投票機制決定,肯定是奇數個節點)。多節點證明它們之間肯定會有數據的通信,同時,爲了能夠使zk集羣對外是透明的,一個整體對外提供服務,那麼客戶端訪問zk服務器的數據肯定是要數據同步,也即數據一致性。

zk集羣是Leader/Follower模式來保證數據同步的。整個集羣同一時刻只能有一個Leader,其他都是Follower或Observer。Leader是通過選舉選出來的,這裏涉及到ZAB協議(原子消息廣播協議)。

1 ZK數據一致性

  • ① 順序一致性(CAP理論的C)

來自任意特定客戶端的更新都會按其發送順序被提交。也就是說,如果一個客戶端將Znode z的值更新爲a,在之後的操作中,它又將z的值更新爲b,則沒有客戶端能夠在看到z的值是b之後再看到值a(如果沒有其他對z的更新)。

  • ② 原子性(CAP理論的A)

每個更新要麼成功,要麼失敗。這意味着如果一個更新失敗,則不會有客戶端會看到這個更新的結果。

  • ③ 單一系統映像

一個客戶端無論連接到哪一臺服務器,它看到的都是同樣的系統視圖。這意味着,如果一個客戶端在同一個會話中連接到一臺新的服務器,它所看到的系統狀態不會比 在之前服務器上所看到的更老。當一臺服務器出現故障,導致它的一個客戶端需要嘗試連接集合體中其他的服務器時,所有滯後於故障服務器的服務器都不會接受該 連接請求,除非這些服務器趕上故障服務器。

  • ④ 持久性

一個更新一旦成功,其結果就會持久存在並且不會被撤銷。這表明更新不會受到服務器故障的影響。

1.1 ZK選舉

  • 何時觸發選舉

選舉Leader不是隨時選舉的,畢竟選舉有產生大量的通信,造成網絡IO的消耗。因此下面情況纔會出現選舉:

集羣啓動
服務器處於尋找Leader狀態
當服務器處於LOOKING狀態時,表示當前沒有Leader,需要進入選舉流程
崩潰恢復
Leader宕機
網絡原因導致過半節點與Leader心跳中斷
  • 如何成爲Leader
數據新舊程度
只有擁有最新數據的節點纔能有機會成爲Leader
通過zxid的大小來表示數據的新,zxid越大代表數據越新
myid
集羣啓動時,會在data目錄下配置myid文件,裏面的數字代表當前zk服務器節點的編號
當zk服務器節點數據一樣新時, myid中數字越大的就會被選舉成ОLeader
當集羣中已經有Leader時,新加入的節點不會影響原來的集羣
投票數量
只有得到集羣中多半的投票,才能成爲Leader
多半即:n/2+1,其中n爲集羣中的節點數量
  • 重要的zxid

zxid是判斷能否成爲Leader的條件之一,它代表服務器的數據版本的新舊程度。

zxid由兩部分構成:主進程週期epoch和事務單調遞增的計數器。zxid是一個64位的數,高32位代表主進程週期epoch,低32位代表事務單調遞增的計數器。

主進程週期epoch也叫epoch,是選舉的輪次,每選舉一次就遞增1。事務單調遞增的計數器在每次選舉完成之後就會從0開始。

如果是比較數據新舊的話,直接比較就可以了。因爲如果是主進程週期越大,即高32位越大,那麼低32位就不用再看了。如果主進程週期一致,低32位越大,整個zxid就越大。所以直接比較整個64位就可以了,不必高32位於高32位對比,低32位與低32位比較。

示例如下:

在選舉的時候是投票方式進行的,除主進程週期外,投票格式爲(myid,zxid)。

第一種情況,比較容易理解,下面以3臺機器爲例子。

    三個zk節點A,B,C,三者開始都沒有數據,即Zxid一致,對應的myid爲1,2,3。
    A啓動myid爲1的節點,zxid爲0,此時只有一臺服務器無法選舉出Leader
    B啓動myid爲2的節點,zxid爲0,B的zxid與A一樣,比較myid,B的myid爲2比A爲1大,B成Leader
    C啓動myid爲3的節點,因爲已經有Leader節點,則C直接加入集羣,承認B是leader

第二種情況,已5臺機器爲例子。

    五個節點A,B,C,D,E,B是Leader,其他是Follower,myid分別爲1,2,3,4,5,zxid分別爲3,4,5,6,6。運行到某個時刻時A,B掉線或宕機,此時剩下C D E。在同一輪選舉中,C,D,E分別投自己和交叉投票。
    第一次投票,都是投自己。
    投票情況爲:C:(3,5) D:(4,6) E:(5,6)。
    同時也會收到其他機器的投票。
    投票情況爲:C:(3,5)(4,6)(5,6),D:(4,6)(3,5)(5,6),E:(5,6)(4,6)(3,5)
    機器內部會根據選主原則對比投票,變更投票,投票情況爲:C:(3,5)(4,6)(5,6)【不變更】。 D:(4,6)(4,6)(5,6)【變更】。E:(5,6)(5,6)(5,6)【變更】
    統計票數,C-1票,D-3票,E-5票。因此E成爲Leader。

接下來就是對新Leader節點的檢查,數據同步,廣播,對外提供服務。

注:具體選舉過程請參考 40_ZooKeeper選舉制度.md

1.2 ZK原子廣播機制(ZAB協議)

Zab協議有兩種模式,分別是恢復模式和廣播模式。

graph LR
A[Zab]-->B[恢復模式]
A-->C[廣播模式]
  • 恢復模式

當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server完成了和leader的狀態同步以後,恢復模式就結束了。狀態同步保證了leader和server具有相同的系統狀態。

  • 廣播模式

一旦Leader已經和多數的Follower進行了狀態同步後,他就可以開始廣播消息了,即進入廣播狀態。這時候當一個Server加入ZooKeeper服務中,它會在恢復模式下啓動,發現Leader,並和Leader進行狀態同步。待到同步結束,它也參與消息廣播。ZooKeeper服務一直維持在Broadcast狀態,直到Leader崩潰了或者Leader失去了大部分的Followers支持。

1.2.1 廣播模式

廣播模式類似一個簡單的兩階段提交:接收讀寫請求,廣播發送後待收到ack後,提交事務。

graph TB
A[leader]-->|1.proposal| B[follower]
A[leader]-->|1.proposal| C[follower]
B-->|2.ack|A
C-->|2.ack|A
A-->|3.commit|B
A-->|3.commit|C

廣播協議在所有的通訊過程中使用TCP的FIFO信道,通過使用該信道,使保持有序性變得非常的容易。通過FIFO信道,消息被有序的deliver。只要收到的消息一被處理,其順序就會被保存下來。

Leader會廣播已經被deliver的Proposal消息。在發出一個Proposal消息前,Leader會分配給Proposal一個單調遞增的唯一id,稱之爲zxid。因爲Zab保證了因果有序, 所以遞交的消息也會按照zxid進行排序。廣播是把Proposal封裝到消息當中,並添加到指向Follower的輸出隊列中,通過FIFO信道發送到 Follower。當Follower收到一個Proposal時,會將其寫入到磁盤,可以的話進行批量寫入。一旦被寫入到磁盤媒介當 中,Follower就會發送一個ACK給Leader。 當Leader收到了指定數量的ACK時,Leader將廣播commit消息並在本地deliver該消息。當收到Leader發來commit消息時,Follower也會遞交該消息。過程如下:

  1. 集羣選舉完成,並且完成數據同步後,開始對外服務,接收讀寫請求
  2. 當leader接收到客戶端新的事務請求後,會生成對新的事務proposal,並根據zxid的順序向所有的follower分發事務proposal
  3. 當follower收到leader的proposal時,根據接收的先後順序處理proposal
  4. 當Leader收到follower針對某個proposal過半的ack後,則發起事務提交,重新發起一個commit的proposal
  5. Follower收到commit的proposal後,記錄事務提交,並把數據更新到內存數據庫
# 補充說明
由於只有過半的機器給出反饋,則可能存在某時刻某些節點數據不是最新的
如果需要確定讀取到的數據是最新的,則可以在讀取之前,調用sync方法進行數據同步

需要注意的是,該簡化的兩階段提交自身並不能解決Leader故障,所以添加恢復模式來解決Leader故障。

1.2.2 恢復模式

恢復階段概述

正常工作時,Zab協議會一直處於廣播模式,直到Leader故障或失去了指定數量的Followers。 爲了保證進度,恢復過程中必須選舉出一個新Leader,並且最終讓所有的Server擁有一個正確的狀態。對於Leader選舉,需要一個能夠成功高几率的保證存活的算法。Leader選舉協議,不僅能夠讓一個Leader得知它是leader,並且有指定數量的Follower同意該決定。如果Leader選舉階段發生錯誤,那麼Servers將不會取得進展。最終會發生超時,重新進行Leader選舉。在我們的實現中,Leader選舉有兩種不同的實現方式。如果有指定數量的Server正常運行,快速選舉的完成只需要幾百毫秒。

恢復階段的保證

該恢復過程的複雜部分是在一個給定的時間內,提議衝突的絕對數量。最大數量衝突提議是一個可配置的選項,但是默認是1000。爲了使該協議能夠即使在Leader故障的情況下也能正常運作。我們需要做出兩條具體的保證:

① 我們絕不能遺忘已經被deliver的消息,若一條消息在一臺機器上被deliver,那麼該消息必須將在每臺機器上deliver。
② 我們必須丟棄已經被skip的消息。

保證的實現

如果Leader選舉協議保證了新Leader在Quorum Server中具有最高的提議編號,即Zxid最高。那麼新選舉出來的leader將具有所有已deliver的消息。新選舉出來的Leader,在提出一個新消息之前,首先要保證事務日誌中的所有消息都由Quorum Follower已Propose並deliver。需要注意的是,我們可以讓新Leader成爲一個用最高zxid來處理事務的server,來作爲一個優化。這樣,作爲新被選舉出來的Leader,就不必去從一組Followers中找出包含最高zxid的Followers和獲取丟失的事務。

    • ① 第一條

所有的正確啓動的Servers,將會成爲Leader或者跟隨一個Leader。Leader能夠確保它的Followers看到所有的提議,並deliver所有已經deliver的消息。通過將新連接上的Follower所沒有見過的所有PROPOSAL進行排隊,並之後對該Proposals的COMMIT消息進行排隊,直到最後一個COMMIT消息。在所有這樣的消息已經排好隊之後,Leader將會把Follower加入到廣播列表,以便今後的提議和確認。這一條是爲了保證一致性,因爲如果一條消息P已經在舊Leader-Server1中deliver了,即使它剛剛將消息P deliver之後就掛了,但是當舊Leader-Server1重啓恢復之後,我們的Client就可以從該Server中看到該消息P deliver的事務,所以爲了保證每一個client都能看到一個一致性的視圖,我們需要將該消息在每個Server上deliver。

    • ② 第二條

skip已經Propose,但不能deliver的消息,處理起來也比較簡單。在我們的實現中,Zxid是由64位數字組成的,低32位用作簡單計數器。高32位是一個epoch。每當新Leader接管它時,將獲取日誌中Zxid最大的epoch,新Leader Zxid的epoch位設置爲epoch+1,counter位設置0。用epoch來標記領導關係的改變,並要求Quorum Servers 通過epoch來識別該leader,避免了多個Leader用同一個Zxid發佈不同的提議。

這個方案的一個優點就是,可以skip一個失敗的領導者的實例,從而加速並簡化了恢復過程。如果一臺宕機的Server重啓,並帶有未發佈的 Proposal,那麼先前的未發佈的所有提議將永不會被deliver。並且它不能夠成爲一個新leader,因爲任何一種可能的Quorum Servers,都會有一個Server其Proposal 來自於一個新epoch,因此它具有一個較高的zxid。

1.2.3 Paxos與Zab一致性對比

Paxos一致性

Paxos的一致性不能達到ZooKeeper的要 求,我們可以下面一個例子。我們假設ZK集羣由三臺機器組成,Server1、Server2、Server3。Server1爲Leader,他生成了 三條Proposal,P1、P2、P3。但是在發送完P1之後,Server1就掛了。

Server1掛掉之後,Server3被選舉成爲Leader,因爲在Server3裏只有一條Proposal—P1。所以,Server3在P1的基礎之上又發出了一條新Proposal—P2',P2'的Zxid爲02。

Server2發送完P2'之後,它也掛了。此時Server1已經重啓恢復,並再次成爲了Leader。那麼,Server1將發送還沒有被deliver的Proposal—P2和P3。由於Follower-Server2中P2'的Zxid爲02和Leader-Server1中P2的Zxid相等,所以P2會被拒絕。而P3,將會被Server2接受。

我們分析一下Follower-Server2中的Proposal,由於P2’將P2的內容覆蓋了。所以導致,Server2中的Proposal-P3無法生效,因爲他的父節點並不存在。

Zab一致性

首先來分析一下,上面的示例中爲什麼不滿足ZooKeeper需求。ZooKeeper是一個樹形結構,很多操作都要先檢查才能確定能不能執行,比如,在圖3.8中Server2有三條Proposal。P1的事務是創建節點"/zk",P2’是創建節點"/c",而P3是創建節點 “/a/b”,由於"/a"還沒建,創建"a/b"就搞不定了。那麼,我們就能從此看出Paxos的一致性達不到ZooKeeper一致性的要求。

爲了達到ZooKeeper所需要的一致性,ZooKeeper採用了Zab協議。Zab做了如下幾條保證,來達到ZooKeeper要求的一致性。

  • Zab要保證同一個leader的發起的事務要按順序被apply,同時還要保證只有先前的leader的所有事務都被apply之後,新選的leader才能在發起事務。
  • 一些已經Skip的消息,需要仍然被Skip。

我想對於第一條保證大家都能理解,它主要是爲了保證每 個Server的數據視圖的一致性。我重點解釋一下第二條,它是如何實現。爲了能夠實現,Skip已經被skip的消息。我們在Zxid中引入了 epoch,如下圖所示。每當Leader發生變換時,epoch位就加1,counter位置0。

我們繼續使用上面的例子,看一下他是如何實現Zab的 第二條保證的。我們假設ZK集羣由三臺機器組成,Server1、Server2、Server3。Server1爲Leader,他生成了三條 Proposal,P1、P2、P3。但是在發送完P1之後,Server1就掛了。

Server1掛掉之後,Server3被選舉成爲 Leader,因爲在Server3裏只有一條Proposal—P1。所以,Server3在P1的基礎之上又發出了一條新Proposal—P2', 由於Leader發生了變換,epoch要加1,所以epoch由原來的0變成了1,而counter要置0。那麼,P2'的Zxid爲10。

Server2發送完P2'之後,它也掛了。此時Server1已經重啓恢復,並再次成爲了Leader。那麼,Server1將發送還沒有被deliver的Proposal—P2和P3。由於Server2中P2'的Zxid爲10,而Leader-Server1中P2和P3的Zxid分別爲02和03,P2'的epoch位高於P2和P3。所以此時Leader-Server1的P2和P3都會被拒絕,那麼我們Zab的第二條保證也就實現了。

1.3 ZK數據同步機制

1.3.1 同步準備

完成選舉之後,爲了數據一致性,需要進行數據同步流程。

  • Leader準備

    Leader告訴其它follower當前最新數據是什麼即zxid
    Leader構建一個NEWLEADER的包,包括當前最大的zxid,發送給所有的follower或者Observer
    Leader給每個follower創建一個線程LearnerHandler來負責處理每個follower的數據同步請求,同時主線程開始阻塞,等到超過一半的follwer同步完成,同步過程才完成,leader才真正成爲leader

  • Follower準備

    選舉完成後,嘗試與leader建立同步連接,如果一段時間沒有連接上就報連接超時,重新回到選舉狀態FOLLOWING
    向leader發送FOLLOWERINFO包,帶上follower自己最大的zxid

1.3.2 同步初始化

同步初始化涉及到三個東西:minCommittedLog、maxCommittedLog、zxid
– minCommittedLog:最小的事務日誌id,即zxid沒有被快照存儲的日誌文件的第一條,每次快照存儲
完,會重新生成一個事務日誌文件
– maxCommittedLog:事務日誌中最大的事務,即zxid

1.3.3 數據同步場景
直接差異化同步(DIFF同步)
僅回滾同步TRUNCͨ,即刪除多餘的事務日誌,比如原來的Leader宕機後又重新加入,可能存在它自己寫
入提交但是別的節點還沒來得及提交
先回滾再差異化同步(TRUNC+DIFF同步)
全量同步(SNAP同步)

不同的數據同步算法適用不同的場景。

ZK讀寫機制

ZK寫機制

  • client向zk中的server發送寫請求,如果該server不是leader,則會將該寫請求轉發給leader server,leader將請求事務以proposal形式分發給follower;
  • 當follower收到收到leader的proposal時,根據接收的先後順序處理proposal;
  • 當Leader收到follower針對某個proposal過半的ack後,則發起事務提交,重新發起一個commit的proposal
  • Follower收到commit的proposal後,記錄事務提交,並把數據更新到內存數據庫;
  • 當寫成功後,反饋給client。

ZK讀機制

  • client可以對zk中的任意一個server(因爲zk數據是一致性的)發出讀請求,然後server反饋給client讀出的數據結果。

引用:https://www.jianshu.com/p/57fecbe70540
| http://www.cnblogs.com/wuxl360/p/5817646.html | 部分內容參考了一篇論文“A simple totally ordered broadcast protocol”


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