Zookeeper ZAB协议原理浅析
文章目錄
- 前言
- 1. 基本角色和概念
- 2. Leader Election
- 3. Discovery
- 4. Synchronization
- 5. BroadCast
- 后記
前言
DTCC 要在下周一到周三要在北京舉辦,身邊有不少人都去參加了,領略中國最為領先的一些公司的自研存儲技術。
阿里自研polardb,polardb-x(x-engine)相關,華為自研Gaussdb,開源TiDB 等,從SQL,到NoSQL,到NewSQL 都會將一些核心技術設計在大會上分享討論,而且今年也是中國數據存儲技術進入時間領先領域的一年(之前都是google,微軟等巨頭),那這場大會將是深入了解近年來中國存儲最為前沿的技術盛會。
然后周五的一場技術分享卻發現自己知識體系的嚴重漏洞, 分布式領域從上到下(分布式協調系統,分布式數據庫(kv,table,graph,document),分布式存儲(塊,文件系統,對象),單機存儲),除了最底層的單機存儲引擎(現在做的)之外沒有一個能夠深入理解掌握并靈活應用的。僅僅為了準備一個分布式協調系統的分享,就發現了無數的知識漏洞(從基礎的編碼到上層的系統認知),那這樣的基礎去參加更高層次的技術集會豈不是被吊打。就像平時聽大佬們分享一樣,總是在感嘆自己的無知,沒有足夠的知識基礎,沒有辦法在大腦中快速打造屬于自己的知識架構,最后反而適得其反。
回到我們要討論的ZAB協議上,這是周內分享的一部分,當然僅僅是一些原理上的描述,并沒有涉及zookeeper內部的代碼實現。分享過程中將ZAB協議的演進也做了一個整體的分享,從paxos,multi paxos, raft/zab 都做了整體的描述,本篇文章主要討論zab的協議原理,畢竟zookeeper的實現核心,當然要上干貨。
之前有兩篇相關的zookeeper 基礎入門和運維相關的文章,能夠有效節省大家的入門時間,先對 zookeeper有一個整體的了解。
1. 一文入門zookeeper
2. 一文運維zookeeper
關于zab協議內容的介紹能夠回答如下幾個問題:
- zookeeper 客戶端接口為什么是wait-free的?即接口之間不會相互影響,所以不需要相互等待返回,可以并行調用接口。(并行更新數據)
- zookeeper 如何保證不出現雙主?
- zookeeper如何保證請求順序執行?
1. 基本角色和概念
ZAB 協議(zookeeper atomic broadcast) zookeeper原子廣播協議,作為zookeeper實現分布式協調服務的核心,提供從leader 選舉,到日志復制,到數據同步 以及 最后的數據廣播 ,提供了一整套的實現算法。是一個值得學習研究的分布式系統,能夠極大得幫助一些感興趣的同學提升對分布式系統的理解和認知。
zookeeper 內部有三種角色,每一種角色可以用一個zookeeper server進程來表示:
- leader: 整個集群只能有一個,主要處理寫請求,zookeeper中所有的寫請求都需要由leader負責處理。當然也能提供讀服務。
- follower: 整個集群可以有多個,只能提供讀服務。在leader發生異常之后通過ZAB的leader election 以及后續的Discovery完成leader的重新選舉,將一個follower 標記為leader,對外提供讀寫服務。
- observer: 不參與leader選舉和投票,僅僅提供讀服務和接受leader變更的通知。可以作為zookeeper同城雙機房,中的從機房的角色。既能夠提供讀服務,又能夠有效得減少兩個機房之間的rpc通信,從而提升整體的集群性能(當然,存在的問題也很明顯,主從機房發生網絡分區,從機房就不可用了)。
ZAB協議的主要是四個階段:
- Leader Election: 主要是節點之間進行信息同步,選擇出一個leader
- Discovery: leader 獲取最新的history信息。(這里的history信息是整個集群最新的<v,zxid> 事務版本zxid以及其對應的數據)
- Synchronization: leader將獲取到的最新的數據同步到其他的從節點,并補全老數據,刪除新數據
- BroadCast: 之前的三個階段都是集群不可用的狀態。到了這個階段,整個集群就可以對外提供讀寫服務,且zookeeper集群正常狀態下處于該階段。
ZAB 協議中有一些基本概念需要提前同步一下,如果感覺理解的還不很深刻,建議先看看前言推薦的兩篇文章。
zxid: 唯一標識一個trasaction, 全局唯一遞增的64位整數。zxid由 <epoch, count>Epoch: 每個leader生命周期的一個標識。newEpoch = lastEpoch + 1Count:表示每個Epoch期間發生的transaction id, 每個count 都是從0開始加一遞增- zxid的比較; 我們稱zxid <e, c> 大于 zxid’<e’, c’>,當滿足 (e > e’ ) || (e = e’ & c > c’).
同時在ZAB中,我們前面說到的zookeeper內部的角色都會統稱為peer, 一個peer代表一個角色進程;peer中有如下幾個核心變量:
history: 被Peer提交的歷史proposal<v,zxid>,也就是數據和事務idacceptedEpoch,接受最新的NEWEPOCH的Epoch,主要是用來leader選舉過程中follower判斷是否接受leader的NEWEPOCH信息。currentEpoch接受最新的NEWLEADER的epoch, 當選舉出來新的 leader之后,會將新leader的epoch更新到這個文件中。lastZxid: 表示history 最近提交的proposal的zxid.
這么多概念第一次看肯定記不下, 實際講解的過程中如果忘記了可以返回來查看。
2. Leader Election
顧名思義,這個階段就是選舉leader的階段,集群不可用。
核心目的:通過投票完成leader選舉,且集群每一個成員都會知道leader的epoch和leader id(myid文件)
時序圖如下:
投票的信息主要是類似vote (zxid,id),當然實際更加詳細(vote,id,state,round);id表示 唯一標識一個peer的id,也就是myid文件中的編號;state表示peer的所處的狀態( leader,follower,election),round表示當前peer是第幾輪投票。
上圖中我們使用node(id,zxid)來簡化選舉過程,其中id就是myid, zxid就是當前peer最新的版本號。
圖中有三條白色箭頭,分別代表三個節點的時間,每一個節點在某一個時間會有三條線。
比如T1時刻的node1的三條黃色線,表示分別向自己投票,將自己的投票信息發送給其他兩個節點,投票信息的大小比較如下規則:node1(id,zxid) > node2(id’ , zxid’) ,當滿足 zxid > zxid’ || (zxid==zxid’ && id > id’)
T1 時刻 開始了leader選舉,三個節點都將各自的node信息先發送給自己,再發送給其他兩個節點。
T2 時刻, 其他兩個節點都已經完成了投票信息的比較:
- 比如 node1 會收到其他兩個節點的投票信息,依次和自己的zxid進行比較,版本號高的成為leader;node1最高,不需要再發送消息投票(它開始已經投自己一票了)。
- node2 會收到來自node3和node1的投票,進行投票信息的比較,發現node1 > node2,node1 > node3,投票給node1,并準備好 新的投票結果進行廣播。
- node3 類似,投票給node1。
T3 時刻,整個集群其實已經完成了選舉,node1成為新leader, 不過還是準leader,后續需要進行一些更進一步的版本數據同步。
這個過程存在一些問題,比如node1 T1時刻發送給 node2節點的投票信息出現rpc延遲,在node2完成投票決策之后才到達node2。
在T1 時候 , node2只收到了node2自己和node3的投票,進行投票信息的比對,雖然zxid 相等,但是node3 id更大,且也滿足大多數,則node2 會選擇node3作為leader,而node3會選擇node1進行投票(node1發送到node3的投票信息并沒有延遲)也就是到T3時刻,node2認為node3是leader , 而node3和node1都認為自己是leader。當然實際情況node3并不會被標記為leader,因為node3只收到一個投票,不滿足大多數,只是集群中會存在這樣的沖突。
當然這種問題在后續的Dicovery階段進行leader版本信息比較時就能夠避免,發現leader的版本號比follower 版本號更低時會觸發重新選舉,這里說一下Leader Election這個階段如何避免 某個peer出現 delay message的問題。
維護一個超時時間 Finalize Wait Time,當某一個peer收到投票信息后發送了一次投票結果,但是在這段時間內如果還收到其他的投票信息且需要變更投票結果,那么這個peer會重新發送一個新的決策結果給其他的peer。
也就是到這個Finalize Wait Time 結束后的集群leader才會是新的Leader。
T2時刻也處在FWT的時間段內,這個時候延遲的node1 的投票信息發送到了node2,發現之前的投票結果需要變更,則會重新發起一次投票,投票為node1作為leader。
當然這個 Finalize Wait Time 肯定也不能完全保證解決這個問題,它的數值設置多大也只是概率性的降低delay message 導致的投票信息延遲達到的問題。所以,還需要更加嚴謹的機制來保證不出現leader沖突的問題,我們繼續來看后續階段。
3. Discovery
Leader Election之后整個集群已經完成了選主,當然這個leader并不是真正的leader。之前它可能擁有最新的版本號,但現在已經改朝換代了,需要有自己的年號來向天下宣告自己的登基。所以它需要重新發布年號(版本號),同時需要掌控整個王朝最新的資源(最新的數據),只有完成這一些事情自己才能穩坐寶座,成為正王。
現在的集群擁有這個幾個角色,其中有一個準leader(按照之前leader選舉過程,也有可能出現雙leader,然后進入這個階段)
ZAB協議的角度先總體說一下這個階段的核心目的:
- 確認/生成 一個新leader的Epoch
- 新的Epoch同步到所有的Follower
- Leader獲取最新的history, 準備進行后續的Synchronization 階段。history: <value, zxid>
具體過程如下:
-
Follower節點知道準Leader節點之后,會發送一個
FOLLOWERINFO的信息攜帶自己的f.acceptedEpoch內容 -
準leader節點收到超過半數的
FOLLOWERINFO之后,會從中選擇一個最大的,并在最大的基礎上+1,即max{f.acceptedEpoch} + 1 -
準Leader將準備好的
NEWEPOCH發送到follower, 表示自己的年號已經更新。等待quorum中的成員回復ACK -
follower 收到
NEWEPOCH之后和自己本地epoch進行比對:a. leader 發送過來的epoch > acceptedEpoch,更新自己的acceptedEpoch 為新的epoch,并回復一個ACKEPOCH消息,這個消息中攜帶上個currentEpoch, history 和 lastZxid(history 最近提交的proposal的zxid)
b. leader發送過來的epoch < acceptedEpoch ,則 回退到階段0,重新進行leader選舉(集群中存在節點異常)。
c. leader發送過來的epoch 和 本地acceptedEpoch相等的場景論文并沒有提到,感覺應該會需要重新選舉,畢竟leader已經在收到大多數的FOLLOWERINFO 中最大的+1了。 -
Leader收到所有quorum中follower的ACKEPOCH, 從所有的消息中找出currentEpoch最大的或者lastZxid最大的follower,然后把該follower的history 作為自己的history(pull history的過程)。當然,如果本地自己的currentEpoch 或者 lastZxid最大,那就用本地的history即可。
到現在,Leader 已經獲取到最新的history, 并開始準備進行后續的Synchronization 階段。
4. Synchronization
這個階段的核心目的是:
- 同步history proposal。即將Leader獲取到的最新的history 數據同步到follower節點,讓整個集群數據對齊。
- 處理上個階段遺留下來的proposal,follower節點中的數據 需要清理的可以清理,需要刪除的可以刪除。
大體過程如下:
Leader這個階段剛開始的時候已經有了整個集群最新的history數據。
-
Leader 想所有的follower發送
NEWLEADER信息,其中包括leader自己最新的epoch 和 最新的history數據。 -
follower 收到leader的消息之后判斷當前輪次自己的acceptedEpoch和leader發送過來的epoch是否一樣(discovery階段已經對follower自己的acceptedEpoch進行了更新)
1). follower的acceptedEpoch和新epoch相同,表示自己已經跟上了新的epoch, 那么做如下幾個事情
a. 更新自己的currentEpoch為新的epoch,表示進入新的朝代了
b. 按照zxid的大小逐一進行本地proposed,此時這些transaction還未commit。
c. 更新自己的history為最新的history
d. 返回一個ACKNEWLEADER 給leader, 表示這個follower已經完成數據同步2). follower收到的epoch和本地的acceptedEpoch不同,那么回退到階段0,重新選主(存在節點異常,當前主節點并不能包含所有的數據,不能隨意更新,否則會丟數據)。
-
Leader 收到follower節點的ACKNEWLEADER消息之后,對proposal的數據進行提交commit,所有的follower節點也會收到commit請求(落盤)
-
follower節點收到leader的COMMIT請求,會對自己本地已經proposed但還未commit的事務,按照zxid進行從小到大的排序,優先commit zxid較小的節點。
-
Leader 和 Follower都完成同步之后進入第四階段。
從朝代更替來看,前面的幾個階段整個國家處于亂世:無君,君臣各有所思,各有所謀,可能的多君。。。。
到這個階段,歷經千難萬險完成了國家統一,君強臣明,整個國家開始一致對外,共向繁榮的場景。
正如我們的春秋戰國到秦,五代十國到宋,南宋到元,每一個帝國的崛起都歷經無數次的嘗試和磨難,但大一統的目標從秦遍成了唯一,只有集群大一統,才能夠更好得施展每一個角色的才華。
5. BroadCast
這是一個穩定的時代, 之前三個階段,zookeeper無法對外提供服務。到了這個階段,整個集群即能夠對外提供讀寫服務。
這個階段如果發生集群成員變更,即加入了follower和observer。
整體過程如下:
- Leader收到一個寫請求,會生成一個Proposal: <value, zxid>, zxid = lastZxid + 1,對quorum中的follower節點發起propose請求,并攜帶生成的Proposal。
- follower節點收到propose的proposal,將其加入到history隊列,并向leader回復ACK,表示已經收到propsal。
- leader收到過半節點的ACK之后,認為可以進行commit,則向quorum發送COMMIT請求
- follower收到propose的commit 之后開始進行提交
1). 為了滿足zxid的全局一致性,這里會檢查follower本地是否有未提交的proposal<v,z>,保證比當前zxid小的propose先提交
2). 當所有小于zxid的propose都完成commit之后再提交當前的zxid。 - BroadCast階段 也能夠接受新的Follower或者Observer的加入,步驟如下:
1). 新加入的節點會給Leader發送一個FOLLOWERINFO信息
2). Leader收到后會回復給他一個NEWEPOCH 和 NEWLEADER, 告訴這個節點集群最新的epoch和history數據
3). 新節點收到NEWLEADER后,如果正常邏輯處理完成后(將history中的數據并發propose),回一個ACKNEWLEADER給Leader
4). Leader收到ACK回復之后告訴他可以進行本地proposal的提交了,會發送一個COMMIT 請求
5). 新節點收到這個請求,對本地完成proposed的數據按照zxid從小到達進行commit(落盤)。
6). 新節點完成commit之后,leader會將新的節點加入到自己的quorum列表中。
ps :
a. 以上過程不論是leader還是follower 節點在進行propose的過程都是可以并發進行的。對于leader來說,一個proposal的發起不會等待上一個commit完成之后才會發起,當前proposal和上一個proposal是可以并行處理,保證了zookeeper的更新接口可以提供wait-free 的能力。b. commit 時需要保證本地比當前zxid更小的事務優先提交,從而保證zookeeper的Linearizable 特性。
以上基本就是ZAB協議的每個階段的細節,在我們實際zookeeper的實現中,會對以上四個階段做優化。
我們能夠看到從開始選主到能夠提供服務,這個過程還是會有大量的rpc 和數據交互,zookeeper實際將leader election和discovery變更為 FLE(Fast Leader Election)階段,在完成Leader 選舉之后 leader 就已經擁有了最新的history數據。
將Synchronization 階段變更為了Recovery 過程,整體上就是讓單次rpc攜帶的數據量更大,能夠在完成相互的交流通信之后進行更多更快的本地計算,而不是將較多的時間消耗在rpc和等待rpc數據的過程中。
后記
這是一個亂世的結束,但從歷史的角度看,也會是一個亂世的開始。
居安思危很難,領導層 只能夠在大多數場景做出正確的決策,但p9999和max之間差異還是太大。我們的系統 同樣是一個復雜體系,大量的靜默錯誤(硬件損耗,磁盤的bit位反轉,還有大量的底層系統軟件到上層應用軟件的bug)無法保證一個分布式集群 每時每刻都正常運行。只能在有限的人力,有限的資源下最大化我們系統的可用性,創造足夠的價值。正如那一些歷史上的偉大帝國 在他們所在的時代創造了讓后人敬仰的文明。
總結
以上是生活随笔為你收集整理的Zookeeper ZAB协议原理浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go 分布式学习利器(16) -- go
- 下一篇: “元和岁在卯”下一句是什么