Zookeeper一致性協議Zab詳解

簡介

zookeeper是分佈式協調系統,用來協調、同步多服務器之間的狀態,容錯能力強git

一個應用要保證HA,每每須要N個服務器(N>1)提供服務,其中有M臺master,N-M臺slave。這樣一臺掛了,另外N-1臺也能提供服務。因此,數據也會備份成N份散佈在這些服務器上。如今的問題變成了,如何管理這N臺服務器?如何在master節點失敗的時候從新選擇master?如何保證全部服務器存儲的備份數據一致?程序員

若是你碰到上面那些棘手的問題,zookeeper恰好能夠幫到你:github

  • 存儲公共配置信息
  • 跟蹤全部進程運行狀態
  • 集羣中各節點的管理
  • master選主
  • 提供分佈式同步支持(分佈式鎖等功能)

上面列出的是官方提供的功能支持,還有不少其餘功能等待挖掘。面試

當你的系統依賴zookeeper保證HA和一致性的時候,你確定也好奇,zookeeper自己是如何保證這兩個特性的。幕後功臣每每容易被忽視,其實它就是Zookeeper原子廣播協議(Zab協議)。若是你瞭解過二階段提交、paxos算法、raft算法等大名鼎鼎的分佈式一致性算法,Zab確定也不會陌生,由於他們要達到的目的相同,就是保證應用程序的HA和一致性。算法

背景

爲了讀懂後面的內容,有些術語須要瞭解:apache

  • Peer:節點。表明了系統中的進程,每每系統有多個進程,也就有多個節點提供服務
  • Quorum:多數。當有N個Peer,多數就表明Q(Q>N/2)個Peer
  • Leader:主節點,最多存在一個。表明zookeeper系統中主要的工做進程,Leader纔是真正處理全部zookeeper寫請求的節點,寫請求會從Leader廣播到Quorum Follower
  • Follower:從節點,能夠有多個。若是client對zookeeper發起一個讀請求,Follower能夠直接處理。若是client對zookeeper發起一個寫請求,Follower須要轉發到惟一的Leader,再有Leader處理併發起廣播
  • transaction:事務,一次寫請求就表明一次事務
  • zxid:事務id。zk爲了保證消息有序性,提出了事務編號這個概念。zxid是一個二元組<e,c>,e表明選舉紀元(若是將選舉理解更皇帝的選舉,這紀元表明年號),c表明事務的計數,同一紀元內每次出現寫請求,c自增(表明某個年號內通過了多少年)。容易理解,紀元不變時,計數不斷增長,紀元變化時,計數清零
  • lastZxid:peer中存儲的最新的zxid
  • vote:選票,表明二元組<zi,i>。zk會給每一個peer打上惟一標識,即i,二元組的i就表明了選舉的peer,而zi表明了這個peer上最新的zxid
  • ![](http://7xvaaj.com1.z0.glb.clouddn.com/later.png):判斷符號右邊的數據比左邊的數據更新。若是是zxid,z1![](http://7xvaaj.com1.z0.glb.clouddn.com/later.png)z2表明z1.e < z2.e或者z1.e = z2.e,z1.c < z2.c,若是是vote,v1![](http://7xvaaj.com1.z0.glb.clouddn.com/later.png)v2表明v1.zi < v2.zi或者v1.zi = v2.zi,v1.i < v2.i
  • state:節點狀態,目前只有這三種election、leading、following
  • history:記錄全部事務的日誌
  • lastCommittedZxid:最近提交成功的事務
  • oldThreshold:日誌中記錄最先提交成功的事務。之因此設置一個最先的門檻,就是以爲沒有必要保留很是久遠之前的事務,以便減小對內存的佔用以及數據同步帶來的網絡開銷

詳解

Zab Theory and practice這篇論文中解釋說,Zab的理論和實現並不徹底一致。理論就是yahoo發佈的關於zab的論文,實現也就是Zookeeper開源代碼服務器

實現是在理論的基礎之上作了一些優化手段,這些優化是在zookeeper不斷髮展的過程當中給加上的。我接下來的講解,也是參照這篇論文,以zookeeper 3.3.3的版本的實現爲準,並用本身的語言總結。網絡

理論中的協議

隨着系統啓動或者恢復,會經歷Zab協議中描述的以下四個階段併發

  • 階段0:Leader選舉。每一個peer從Quorum peer中選出本身心中的預備leader
  • 階段1:發現。預備leader從Quorum Follower中發現最新的數據,並覆蓋本身的過時數據
  • 階段2:同步。預備leader採用二階段提交的方式將本身的最新數據同步給Quorum Follower。完成這個步驟,預備leader就轉爲正式leader
  • 階段3:廣播。Leader接受寫請求,並經過二階段提交的方式廣播給Quorum Follower

以前我只是簡述了一下理論中的協議,然而理想很骨感,有不少須要改進或者妥協的地方。下面我會一一闡明:分佈式

  • 階段0的選舉leader實際上很簡陋,只是「看對眼」了就選爲預備leader,因此預備leader的數據可能並不是最新
  • 預備leader數據過時,就須要用階段1來彌補,經過互相傳輸數據,來發現最新的數據,並完成預備leader的數據升級
  • 更多的網絡間數據傳輸表明了更大的網絡開銷

協議實現

瞭解了理想的骨感以後,咱們迴歸現實。

真正apache zookeeper在實現上提出設想:優化leader選舉,直接選出最新的Peer做爲預備Leader,這樣就能將階段0和階段1合併,減小網絡上的開銷和多流程的複雜性

由圖可知,代碼實現上,協議被簡化爲三個階段

  • 快速選舉Leader階段:從Quorum Peer中選出數據最新的peer做爲leader
  • 恢復階段:Leader將數據同步給Quorum Follower
  • 廣播階段:Leader接受寫請求,並廣播給Quorum Follower

Talk is cheap.Show me the Code

這時Linus說過的一句話,不管語言多麼有力,只有代碼才能真正展示做者的思想。以前我只是對是協議實現的三個階段作了一番簡述,只有代碼才能真正撥開Zab協議外面那層迷霧。(爲了避免浪費篇幅,這裏採用僞代碼)

快速選舉Leader階段(FLE)

首先初始化一些數據

  • ReceivedVotes:投票箱,存儲投票節點以及當前投票信息
  • OutOfElection:存儲已經成爲Leader、Follower,不參與投票的節點信息,以及它的歷史投票信息
  • 在選票中寫上本身的大名
  • send notification:發起選票通知,該節點會攜帶選票,進入目標節點的隊列中,至關於給本身投了一票,並毛遂自薦給其餘人

若是當前節點處於選舉狀態,則它也會接到選票通知。它會從隊列中不斷輪詢節點,以便獲取選票信息(若是超時,則不斷放鬆超時時間,直至上限)。根據輪詢出來的發送節點的狀態,來作相應的處理。

  • election:若是發送節點的輪次(round)大於本身,說明本身的選舉信息過期,則更新本身的選舉輪次,清空投票箱,更新本身的選票內容,並將新的選票通知給其餘節點;若是發送節點的輪次等於本身,而且投票內容比本身的更新,則只須要更新本身的選票,並通知給其餘節點就行;若是發送節點的輪次小於本身,說明投票內容過時,沒有參考意義,直接忽略。全部未被忽略的選票,都會進入投票箱。最終根據選票箱中的結果,判斷當前節點的選票是否是佔大多數,若是是就根據當前節點的選票選出Leader
  • leading或following:發送節點輪次等於本身,說明發送節點還參與投票,若是發送節點是Leading或者它的選票在選票箱中佔大多數,則直接完成選舉;若是發送節點已經完成選舉(輪次不一樣)或者它收集的選票較少,那麼它的信息都會存放在OutOfElection中。當節點不斷完成選舉,OutOfElection中數量逐漸變成Quorum時,就把OutOfElection當作投票箱,從中檢查發送節點的選票是否佔多數,若是是就直接選出Leader

恢復階段

通過FLE,已經選出了日誌中具備最新已提交的事務的節點做爲預備Leader。下面就分Leader和Follower兩個視角來介紹具體實現。

Leader視角

首先,更新lastZxid,將紀元+1,計數清零,宣佈改朝換代啦。而後在每次接收到Follower的數據同步請求時,都會將本身lastZxid反饋回去,表示全部Follower以本身的lastZxid爲準。接下來,根據具體狀況來判斷該如何將數據同步給Follower

  • 若是Leader的歷史提交事務比Follower的最新事務要新,說明Follower的數據有待更新。更新方式取決於,Leader最先事務有沒有比Follower最新事務要新:若是前者更新,說明在Leader看來Follower全部記錄的事務都太過陳舊,沒有保留價值,這時只須要將Leader全部history發給Follower就行(響應SNAP);若是後者更新,說明在Leader看來,Follower從本身的lastZxid開始到Leader日誌的最新事務,都須要同步,因而將這一部分截取併發送給Follower(響應DIFF
  • 若是Leader的歷史提交事務沒有Follower的最新事務新,說明Follower存在沒有提交的事務,這些事務都應該被丟棄(響應TRUNC

當Follower完成同步時,會發送同步ack,當Leader收到Quorum ack時,表示數據同步階段大功告成,進入最後的廣播階段。

Follower視角

通知Leader,表示本身但願能同步Leader中的數據。

  • 當收到Leader的拒絕響應時,說明Leader不認可本身做爲Follower,有可能該Leader並不可靠,因而開始從新開始FLE
  • 當收到SNAP或DIFF響應時,Follower會同步Leader發送過來的事務
  • 當收到TRUNC響應,Follower會丟棄全部未完成的數據

當每一個Follower完成上述的同步過程時,會發送ack給Leader,並進入廣播階段。

廣播階段

進入到這個階段,說明全部數據完成同步,Leader已經轉正。開始zookeper最多見的工做流程:廣播。

廣播階段是真正接受事務請求(寫請求)的階段,也表明了zookeeper正常工做階段。全部節點都能接受客戶端的寫請求,可是Follower會轉發給Leader,只有Leader才能將這些請求轉化成事務,廣播出去。這個節點同樣有兩個角色,下面仍是按照這兩個角色來說解。

Leader視角:

  • Leader必須通過ready,才能接受寫請求。完成ready的Leader不斷接受寫請求,轉化成事務請求,廣播給Quorum Follower。
  • 當Leader接收到ack時,說明Follower完成相應處理,Leader廣播提交請求,Follower完成提交。
  • 當發現新Peer請求做爲Follower加入時,將本身的紀元、事務日誌發送給該Peer,以便它完成上述恢復階段的過程。收到該Peer的同步完成的ack時,Leader會發送提交請求,以便Peer提交全部同步完成的事務。這時,該Peer轉正爲Follower,被Leader歸入Quorum Follower中。

Follower視角:

  • Follower被發現是Leading狀態,則執行ready過程,用來接受寫請求。
  • 當接受到Leader廣播過來的事務請求時,Follower會將事務記錄在history,並響應ack。
  • 當接收到Leader廣播過來的提交請求時,Follower會檢查history中有沒有還沒有提交的事務,若是有,須要等待以前的事務按照FIFO順序提交以後,才能提交本事務。

總結

這篇文章沒有介紹Zookeeper的使用,而是着重講解它的核心協議Zab的實現。正如文中說起,Zab最先的設想和如今的實現並不相同,今日的實現是在Zookeeper不斷髮展壯大的過程當中不斷優化、改進而來的,也許早期的實現就是yahoo論文中構想的那樣。羅馬不是一日建成,任何人都不能期望一口吃個大胖子。若是Zookeeper剛開始就想着如何優化到極致,那反而會嚴重影響到這個項目自己的價值,由於它極可能還沒面試就被淘汰。

We should forget about small e ciencies, say about 97% of the time: premature optimization is the root of all evil.

Knuth提醒咱們,過早優化是萬惡之源。可是同時,一個好的程序員也不會忘記須要優化的那部分,他會定位相應的代碼,而後針對性的修改。這也是zookeeper的開發者所作的。

問題彙總

  1. 通常Leader election算法都收到Quorum節點的選票,爲何?

    基於如下兩點:

    • 防止選出多個Leader,每人一票,同一紀元最多選出一個Leader
    • 當log被提交時,說明Quorum節點存儲了這個log。當進行Leader election的時候,當收到Quorum選票稱爲Leader時,一定存儲了最新被提交的log
相關文章
相關標籤/搜索