本文作者:何建輝(公眾號:org_yijiaoqian)
本文 GitHub org_hejianhui/JAVAStudy 已收錄,有我的系列文章。
前言
- Zookeeper特性與節(jié)點(diǎn)說明
- Zookeeper客戶端使用與集群原理
- Zookeeper典型使用場景實(shí)踐
前三篇講了Zookeeper的特性、客戶端使用和集群原理、典型使用場景實(shí)踐,本篇重點(diǎn)深入了解ZAB協(xié)議以及源碼實(shí)現(xiàn)的解析。
Zookeeper ZAB協(xié)議
什么是Zab協(xié)議?
Zab協(xié)議的全稱是 Zookeeper Atomic Broadcast (Zookeeper原子廣播)。
Zookeeper 是通過 Zab 協(xié)議來保證分布式事務(wù)的最終一致性。
- Zab協(xié)議是為分布式協(xié)調(diào)服務(wù)Zookeeper專門設(shè)計(jì)的一種 支持崩潰恢復(fù) 的 原子廣播協(xié)議 ,是Zookeeper保證數(shù)據(jù)一致性的核心算法。Zab借鑒了Paxos算法,但又不像Paxos那樣,是一種通用的分布式一致性算法。它是特別為Zookeeper設(shè)計(jì)的支持崩潰恢復(fù)的原子廣播協(xié)議。
- 在Zookeeper中主要依賴Zab協(xié)議來實(shí)現(xiàn)數(shù)據(jù)一致性,基于該協(xié)議,zk實(shí)現(xiàn)了一種主備模型(即Leader和Follower模型)的系統(tǒng)架構(gòu)來保證集群中各個(gè)副本之間數(shù)據(jù)的一致性。這里的主備系統(tǒng)架構(gòu)模型,就是指只有一臺(tái)客戶端(Leader)負(fù)責(zé)處理外部的寫事務(wù)請求,然后Leader客戶端將數(shù)據(jù)同步到其他Follower節(jié)點(diǎn)。
Zookeeper 客戶端會(huì)隨機(jī)的鏈接到 zookeeper 集群中的一個(gè)節(jié)點(diǎn),如果是讀請求,就直接從當(dāng)前節(jié)點(diǎn)中讀取數(shù)據(jù);如果是寫請求,那么節(jié)點(diǎn)就會(huì)向 Leader 提交事務(wù),Leader 接收到事務(wù)提交,會(huì)廣播該事務(wù),只要超過半數(shù)節(jié)點(diǎn)寫入成功,該事務(wù)就會(huì)被提交。
Zab 協(xié)議實(shí)現(xiàn)的作用
- 使用一個(gè)單一的主進(jìn)程(Leader)來接收并處理客戶端的事務(wù)請求(也就是寫請求),并采用了Zab的原子廣播協(xié)議,將服務(wù)器數(shù)據(jù)的狀態(tài)變更以 事務(wù)proposal (事務(wù)提議)的形式廣播到所有的副本(Follower)進(jìn)程上去。
- 保證一個(gè)全局的變更序列被順序引用
Zookeeper是一個(gè)樹形結(jié)構(gòu),很多操作都要先檢查才能確定是否可以執(zhí)行,比如P1的事務(wù)t1可能是創(chuàng)建節(jié)點(diǎn)"/a",t2可能是創(chuàng)建節(jié)點(diǎn)"/a/bb",只有先創(chuàng)建了父節(jié)點(diǎn)"/a",才能創(chuàng)建子節(jié)點(diǎn)"/a/b"。
為了保證這一點(diǎn),Zab要保證同一個(gè)Leader發(fā)起的事務(wù)要按順序被Apply,同時(shí)還要保證只有先前Leader的事務(wù)被apply之后,新選舉出來的Leader才能再次發(fā)起事務(wù)。
- 當(dāng)主進(jìn)程出現(xiàn)異常的時(shí)候,整個(gè)zk集群依舊能正常工作。
Zab協(xié)議原理
Zab協(xié)議要求每個(gè) Leader 都要經(jīng)歷三個(gè)階段:發(fā)現(xiàn),同步,廣播。
- 發(fā)現(xiàn):要求zookeeper集群必須選舉出一個(gè) Leader 進(jìn)程,同時(shí) Leader 會(huì)維護(hù)一個(gè) Follower 可用客戶端列表。將來客戶端可以和這些 Follower節(jié)點(diǎn)進(jìn)行通信。
- 同步:Leader 要負(fù)責(zé)將本身的數(shù)據(jù)與 Follower 完成同步,做到多副本存儲(chǔ)。這樣也是體現(xiàn)了CAP中的高可用和分區(qū)容錯(cuò)。Follower將隊(duì)列中未處理完的請求消費(fèi)完成后,寫入本地事務(wù)日志中。
- 廣播:Leader 可以接收客戶端新的事務(wù)Proposal請求,將新的Proposal請求廣播給所有的 Follower。
Zab協(xié)議核心
Zab協(xié)議的核心:定義了事務(wù)請求的處理方式
- 所有的事務(wù)請求必須由一個(gè)全局唯一的服務(wù)器來協(xié)調(diào)處理,這樣的服務(wù)器被叫做 Leader服務(wù)器。其他剩余的服務(wù)器則是 Follower服務(wù)器。
- Leader服務(wù)器 負(fù)責(zé)將一個(gè)客戶端事務(wù)請求,轉(zhuǎn)換成一個(gè) 事務(wù)Proposal,并將該 Proposal 分發(fā)給集群中所有的 Follower 服務(wù)器,也就是向所有 Follower 節(jié)點(diǎn)發(fā)送數(shù)據(jù)廣播請求(或數(shù)據(jù)復(fù)制)
- 分發(fā)之后Leader服務(wù)器需要等待所有Follower服務(wù)器的反饋(Ack請求),在Zab協(xié)議中,只要超過半數(shù)的Follower服務(wù)器進(jìn)行了正確的反饋后(也就是收到半數(shù)以上的Follower的Ack請求),那么 Leader 就會(huì)再次向所有的 Follower服務(wù)器發(fā)送 Commit 消息,要求其將上一個(gè) 事務(wù)proposal 進(jìn)行提交。

Zab協(xié)議內(nèi)容
Zab 協(xié)議包括兩種基本的模式:崩潰恢復(fù) 和 消息廣播
協(xié)議過程
當(dāng)整個(gè)集群啟動(dòng)過程中,或者當(dāng) Leader 服務(wù)器出現(xiàn)網(wǎng)絡(luò)中弄斷、崩潰退出或重啟等異常時(shí),Zab協(xié)議就會(huì) 進(jìn)入崩潰恢復(fù)模式,選舉產(chǎn)生新的Leader。
當(dāng)選舉產(chǎn)生了新的 Leader,同時(shí)集群中有過半的機(jī)器與該 Leader 服務(wù)器完成了狀態(tài)同步(即數(shù)據(jù)同步)之后,Zab協(xié)議就會(huì)退出崩潰恢復(fù)模式,進(jìn)入消息廣播模式。
這時(shí),如果有一臺(tái)遵守Zab協(xié)議的服務(wù)器加入集群,因?yàn)榇藭r(shí)集群中已經(jīng)存在一個(gè)Leader服務(wù)器在廣播消息,那么該新加入的服務(wù)器自動(dòng)進(jìn)入恢復(fù)模式:找到Leader服務(wù)器,并且完成數(shù)據(jù)同步。同步完成后,作為新的Follower一起參與到消息廣播流程中。
協(xié)議狀態(tài)切換
當(dāng)Leader出現(xiàn)崩潰退出或者機(jī)器重啟,亦或是集群中不存在超過半數(shù)的服務(wù)器與Leader保存正常通信,Zab就會(huì)再一次進(jìn)入崩潰恢復(fù),發(fā)起新一輪Leader選舉并實(shí)現(xiàn)數(shù)據(jù)同步。同步完成后又會(huì)進(jìn)入消息廣播模式,接收事務(wù)請求。
保證消息有序
在整個(gè)消息廣播中,Leader會(huì)將每一個(gè)事務(wù)請求轉(zhuǎn)換成對應(yīng)的 proposal 來進(jìn)行廣播,并且在廣播 事務(wù)Proposal 之前,Leader服務(wù)器會(huì)首先為這個(gè)事務(wù)Proposal分配一個(gè)全局單遞增的唯一ID,稱之為事務(wù)ID(即zxid),由于Zab協(xié)議需要保證每一個(gè)消息的嚴(yán)格的順序關(guān)系,因此必須將每一個(gè)proposal按照其zxid的先后順序進(jìn)行排序和處理。
消息廣播
- 在zookeeper集群中,數(shù)據(jù)副本的傳遞策略就是采用消息廣播模式。zookeeper中數(shù)據(jù)副本的同步方式與二段提交相似,但是卻又不同。二段提交要求協(xié)調(diào)者必須等到所有的參與者全部反饋ACK確認(rèn)消息后,再發(fā)送commit消息。要求所有的參與者要么全部成功,要么全部失敗。二段提交會(huì)產(chǎn)生嚴(yán)重的阻塞問題。
- Zab協(xié)議中 Leader 等待 Follower 的ACK反饋消息是指“只要半數(shù)以上的Follower成功反饋即可,不需要收到全部Follower反饋”

消息廣播具體步驟
- 客戶端發(fā)起一個(gè)寫操作請求。
- Leader 服務(wù)器將客戶端的請求轉(zhuǎn)化為事務(wù) Proposal 提案,同時(shí)為每個(gè) Proposal 分配一個(gè)全局的ID,即zxid。
- Leader 服務(wù)器為每個(gè) Follower 服務(wù)器分配一個(gè)單獨(dú)的隊(duì)列,然后將需要廣播的 Proposal 依次放到隊(duì)列中去,并且根據(jù) FIFO 策略進(jìn)行消息發(fā)送。
- Follower 接收到 Proposal 后,會(huì)首先將其以事務(wù)日志的方式寫入本地磁盤中,寫入成功后向 Leader 反饋一個(gè) Ack 響應(yīng)消息。
- Leader 接收到超過半數(shù)以上 Follower 的 Ack 響應(yīng)消息后,即認(rèn)為消息發(fā)送成功,可以發(fā)送 commit 消息。
- Leader 向所有 Follower 廣播 commit 消息,同時(shí)自身也會(huì)完成事務(wù)提交。Follower 接收到 commit 消息后,會(huì)將上一條事務(wù)提交。
zookeeper 采用 Zab 協(xié)議的核心,就是只要有一臺(tái)服務(wù)器提交了 Proposal,就要確保所有的服務(wù)器最終都能正確提交 Proposal。這也是 CAP/BASE 實(shí)現(xiàn)最終一致性的一個(gè)體現(xiàn)。
Leader 服務(wù)器與每一個(gè) Follower 服務(wù)器之間都維護(hù)了一個(gè)單獨(dú)的 FIFO 消息隊(duì)列進(jìn)行收發(fā)消息,使用隊(duì)列消息可以做到異步解耦。 Leader 和 Follower 之間只需要往隊(duì)列中發(fā)消息即可。如果使用同步的方式會(huì)引起阻塞,性能要下降很多。
崩潰恢復(fù)
一旦 Leader 服務(wù)器出現(xiàn)崩潰或者由于網(wǎng)絡(luò)原因?qū)е?Leader 服務(wù)器失去了與過半 Follower 的聯(lián)系,那么就會(huì)進(jìn)入崩潰恢復(fù)模式。
在 Zab 協(xié)議中,為了保證程序的正確運(yùn)行,整個(gè)恢復(fù)過程結(jié)束后需要選舉出一個(gè)新的 Leader 服務(wù)器。因此 Zab 協(xié)議需要一個(gè)高效且可靠的 Leader 選舉算法,從而確保能夠快速選舉出新的 Leader 。
Leader 選舉算法不僅僅需要讓 Leader 自己知道自己已經(jīng)被選舉為 Leader ,同時(shí)還需要讓集群中的所有其他機(jī)器也能夠快速感知到選舉產(chǎn)生的新 Leader 服務(wù)器。
崩潰恢復(fù)主要包括兩部分:Leader選舉 和 數(shù)據(jù)恢復(fù)
Zab 協(xié)議如何保證數(shù)據(jù)一致性
假設(shè)兩種異常情況:
- 一個(gè)事務(wù)在 Leader 上提交了,并且過半的 Folower 都響應(yīng) Ack 了,但是 Leader 在 Commit 消息發(fā)出之前掛了。
- 假設(shè)一個(gè)事務(wù)在 Leader 提出之后,Leader 掛了。
要確保如果發(fā)生上述兩種情況,數(shù)據(jù)還能保持一致性,那么 Zab 協(xié)議選舉算法必須滿足以下要求:
Zab 協(xié)議崩潰恢復(fù)要求滿足以下兩個(gè)要求:
- 確保已經(jīng)被 Leader 提交的 Proposal 必須最終被所有的 Follower 服務(wù)器提交。
- 確保丟棄已經(jīng)被 Leader 提出的但是沒有被提交的 Proposal。
根據(jù)上述要求 Zab協(xié)議需要保證選舉出來的Leader需要滿足以下條件:
- 新選舉出來的 Leader 不能包含未提交的 Proposal 。
即新選舉的 Leader 必須都是已經(jīng)提交了 Proposal 的 Follower 服務(wù)器節(jié)點(diǎn)。
- 新選舉的 Leader 節(jié)點(diǎn)中含有最大的 zxid 。
這樣做的好處是可以避免 Leader 服務(wù)器檢查 Proposal 的提交和丟棄工作。
Zab 如何數(shù)據(jù)同步
- 完成 Leader 選舉后(新的 Leader 具有最高的zxid),在正式開始工作之前(接收事務(wù)請求,然后提出新的 Proposal),Leader 服務(wù)器會(huì)首先確認(rèn)事務(wù)日志中的所有的 Proposal 是否已經(jīng)被集群中過半的服務(wù)器 Commit。
- Leader 服務(wù)器需要確保所有的 Follower 服務(wù)器能夠接收到每一條事務(wù)的 Proposal ,并且能將所有已經(jīng)提交的事務(wù) Proposal 應(yīng)用到內(nèi)存數(shù)據(jù)中。等到 Follower 將所有尚未同步的事務(wù) Proposal 都從 Leader 服務(wù)器上同步過啦并且應(yīng)用到內(nèi)存數(shù)據(jù)中以后,Leader 才會(huì)把該 Follower 加入到真正可用的 Follower 列表中。
Zab 數(shù)據(jù)同步過程中,如何處理需要丟棄的 Proposal
在 Zab 的事務(wù)編號 zxid 設(shè)計(jì)中,zxid是一個(gè)64位的數(shù)字。
其中低32位可以看成一個(gè)簡單的單增計(jì)數(shù)器,針對客戶端每一個(gè)事務(wù)請求,Leader 在產(chǎn)生新的 Proposal 事務(wù)時(shí),都會(huì)對該計(jì)數(shù)器加1。而高32位則代表了 Leader 周期的 epoch 編號。
epoch 編號可以理解為當(dāng)前集群所處的年代,或者周期。每次Leader變更之后都會(huì)在 epoch 的基礎(chǔ)上加1,這樣舊的 Leader 崩潰恢復(fù)之后,其他Follower 也不會(huì)聽它的了,因?yàn)?Follower 只服從epoch最高的 Leader 命令。
每當(dāng)選舉產(chǎn)生一個(gè)新的 Leader ,就會(huì)從這個(gè) Leader 服務(wù)器上取出本地事務(wù)日志最大編號 Proposal 的 zxid,并從 zxid 中解析得到對應(yīng)的 epoch 編號,然后再對其加1,之后該編號就作為新的 epoch 值,并將低32位數(shù)字歸零,由0開始重新生成zxid。
Zab 協(xié)議通過 epoch 編號來區(qū)分 Leader 變化周期,能夠有效避免不同的 Leader 錯(cuò)誤的使用了相同的 zxid 編號提出了不一樣的 Proposal 的異常情況。
基于以上策略: 當(dāng)一個(gè)包含了上一個(gè) Leader 周期中尚未提交過的事務(wù) Proposal 的服務(wù)器啟動(dòng)時(shí),當(dāng)這臺(tái)機(jī)器加入集群中,以 Follower 角色連上 Leader 服務(wù)器后,Leader 服務(wù)器會(huì)根據(jù)自己服務(wù)器上最后提交的 Proposal 來和 Follower 服務(wù)器的 Proposal 進(jìn)行比對,比對的結(jié)果肯定是 Leader 要求 Follower 進(jìn)行一個(gè)回退操作,回退到一個(gè)確實(shí)已經(jīng)被集群中過半機(jī)器 Commit 的最新 Proposal。
Zab實(shí)現(xiàn)原理
Zab 節(jié)點(diǎn)有三種狀態(tài)
- Following:當(dāng)前節(jié)點(diǎn)是跟隨者,服從 Leader 節(jié)點(diǎn)的命令。
- Leading:當(dāng)前節(jié)點(diǎn)是 Leader,負(fù)責(zé)協(xié)調(diào)事務(wù)。
- Election/Looking:節(jié)點(diǎn)處于選舉狀態(tài),正在尋找 Leader。
代碼實(shí)現(xiàn)中,多了一種狀態(tài):Observing 狀態(tài) 這是 Zookeeper 引入 Observer 之后加入的,Observer 不參與選舉,是只讀節(jié)點(diǎn),跟 Zab 協(xié)議沒有關(guān)系。
節(jié)點(diǎn)的持久狀態(tài)
- history:當(dāng)前節(jié)點(diǎn)接收到事務(wù) Proposal 的Log
- acceptedEpoch:Follower 已經(jīng)接收的 Leader 更改 epoch 的 newEpoch 提議。
- currentEpoch:當(dāng)前所處的 Leader 年代
- lastZxid:history 中最近接收到的Proposal 的 zxid(最大zxid)
Zab 的四個(gè)階段
- 選舉階段(Leader Election)
節(jié)點(diǎn)在一開始都處于選舉節(jié)點(diǎn),只要有一個(gè)節(jié)點(diǎn)得到超過半數(shù)節(jié)點(diǎn)的票數(shù),它就可以當(dāng)選準(zhǔn) Leader,只有到達(dá)第三個(gè)階段(也就是同步階段),這個(gè)準(zhǔn) Leader 才會(huì)成為真正的 Leader。
Zookeeper 規(guī)定所有有效的投票都必須在同一個(gè) 輪次 中,每個(gè)服務(wù)器在開始新一輪投票時(shí),都會(huì)對自己維護(hù)的 logicalClock 進(jìn)行自增操作。
每個(gè)服務(wù)器在廣播自己的選票前,會(huì)將自己的投票箱(recvset)清空。該投票箱記錄了所收到的選票。
例如:Server_2 投票給 Server_3,Server_3 投票給 Server_1,則Server_1的投票箱為(2,3)、(3,1)、(1,1)。(每個(gè)服務(wù)器都會(huì)默認(rèn)給自己投票)
前一個(gè)數(shù)字表示投票者,后一個(gè)數(shù)字表示被選舉者。票箱中只會(huì)記錄每一個(gè)投票者的最后一次投票記錄,如果投票者更新自己的選票,則其他服務(wù)器收到該新選票后會(huì)在自己的票箱中更新該服務(wù)器的選票。
這一階段的目的就是為了選出一個(gè)準(zhǔn) Leader ,然后進(jìn)入下一個(gè)階段。
協(xié)議并沒有規(guī)定詳細(xì)的選舉算法,后面會(huì)提到實(shí)現(xiàn)中使用的 Fast Leader Election。

- 發(fā)現(xiàn)階段(Descovery)
在這個(gè)階段,F(xiàn)ollowers 和上一輪選舉出的準(zhǔn) Leader 進(jìn)行通信,同步 Followers 最近接收的事務(wù) Proposal 。
一個(gè) Follower 只會(huì)連接一個(gè) Leader,如果一個(gè) Follower 節(jié)點(diǎn)認(rèn)為另一個(gè) Follower 節(jié)點(diǎn),則會(huì)在嘗試連接時(shí)被拒絕。被拒絕之后,該節(jié)點(diǎn)就會(huì)進(jìn)入 Leader Election階段。
這個(gè)階段的主要目的是發(fā)現(xiàn)當(dāng)前大多數(shù)節(jié)點(diǎn)接收的最新 Proposal,并且準(zhǔn) Leader 生成新的 epoch ,讓 Followers 接收,更新它們的 acceptedEpoch。

- 同步階段(Synchronization)
同步階段主要是利用 Leader 前一階段獲得的最新 Proposal 歷史,同步集群中所有的副本。
只有當(dāng) quorum(超過半數(shù)的節(jié)點(diǎn)) 都同步完成,準(zhǔn) Leader 才會(huì)成為真正的 Leader。Follower 只會(huì)接受 zxid 比自己 lastZxid 大的 Proposal。

- 廣播階段(Broadcast)
到了這個(gè)階段,Zookeeper 集群才能正式對外提供事務(wù)服務(wù),并且 Leader 可以進(jìn)行消息廣播。同時(shí),如果有新的節(jié)點(diǎn)加入,還需要對新節(jié)點(diǎn)進(jìn)行同步。
需要注意的是,Zab 提交事務(wù)并不像 2PC 一樣需要全部 Follower 都 Ack,只需要得到 quorum(超過半數(shù)的節(jié)點(diǎn))的Ack 就可以。

Zab協(xié)議實(shí)現(xiàn)
協(xié)議的 Java 版本實(shí)現(xiàn)跟上面的定義略有不同,選舉階段使用的是 Fast Leader Election(FLE),它包含了步驟1的發(fā)現(xiàn)職責(zé)。因?yàn)镕LE會(huì)選舉擁有最新提議的歷史節(jié)點(diǎn)作為 Leader,這樣就省去了發(fā)現(xiàn)最新提議的步驟。
實(shí)際的實(shí)現(xiàn)將發(fā)現(xiàn)和同步階段合并為 Recovery Phase(恢復(fù)階段),所以,Zab 的實(shí)現(xiàn)實(shí)際上有三個(gè)階段。
Zab協(xié)議三個(gè)階段:
- 選舉(Fast Leader Election)
- 恢復(fù)(Recovery Phase)
- 廣播(Broadcast Phase)
Fast Leader Election(快速選舉)
前面提到的 FLE 會(huì)選舉擁有最新Proposal history (lastZxid最大)的節(jié)點(diǎn)作為 Leader,這樣就省去了發(fā)現(xiàn)最新提議的步驟。這是基于擁有最新提議的節(jié)點(diǎn)也擁有最新的提交記錄
成為 Leader 的條件:
- 選 epoch 最大的
- 若 epoch 相等,選 zxid 最大的
- 若 epoch 和 zxid 相等,選擇 server_id 最大的(zoo.cfg中的myid)
節(jié)點(diǎn)在選舉開始時(shí),都默認(rèn)投票給自己,當(dāng)接收其他節(jié)點(diǎn)的選票時(shí),會(huì)根據(jù)上面的 Leader條件 判斷并且更改自己的選票,然后重新發(fā)送選票給其他節(jié)點(diǎn)。當(dāng)有一個(gè)節(jié)點(diǎn)的得票超過半數(shù),該節(jié)點(diǎn)會(huì)設(shè)置自己的狀態(tài)為 Leading ,其他節(jié)點(diǎn)會(huì)設(shè)置自己的狀態(tài)為 Following。

Recovery Phase(恢復(fù)階段)
這一階段 Follower 發(fā)送他們的 lastZxid 給 Leader,Leader 根據(jù) lastZxid 決定如何同步數(shù)據(jù)。這里的實(shí)現(xiàn)跟前面的 Phase 2 有所不同:Follower 收到 TRUNC 指令會(huì)終止 L.lastCommitedZxid 之后的 Proposal ,收到 DIFF 指令會(huì)接收新的 Proposal。
history.lastCommitedZxid:最近被提交的 Proposal zxid history.oldThreshold:被認(rèn)為已經(jīng)太舊的已經(jīng)提交的 Proposal zxid

Zookeeper ZAB協(xié)議實(shí)現(xiàn)源碼
啟動(dòng)流程
知識(shí)點(diǎn):
- 工程結(jié)構(gòu)介紹
- 啟動(dòng)流程宏觀圖
- 集群啟動(dòng)詳細(xì)流程
- netty 服務(wù)工作機(jī)制
工程結(jié)構(gòu)介紹
項(xiàng)目地址:https://github.com/Apache/zookeeper.git
分支tag :3.6.2
- zookeeper-recipes: 示例源碼
- zookeeper-client: C語言客戶端
- zookeeper-server:主體源碼(包含客戶端)
啟動(dòng)宏觀流程圖

源碼啟動(dòng):
- 服務(wù)端:ZooKeeperServerMain
- 客戶端:ZooKeeperMain
為方便閱讀,以下代碼均省略包名
集群啟動(dòng)詳細(xì)流程
裝載配置:
# zookeeper 啟動(dòng)流程堆棧
>QuorumPeerMain#initializeAndRun //啟動(dòng)工程
>QuorumPeerConfig#parse // 加載config 配置
>QuorumPeerConfig#parseProperties// 解析config配置
>new DatadirCleanupManager // 構(gòu)造一個(gè)數(shù)據(jù)清器
>DatadirCleanupManager#start // 啟動(dòng)定時(shí)任務(wù) 清除過期的快照
代碼堆棧:
>QuorumPeerMain#main //啟動(dòng)main方法
>QuorumPeerConfig#parse // 加載zoo.cfg 文件
>QuorumPeerConfig#parseProperties // 解析配置
>DatadirCleanupManager#start // 啟動(dòng)定時(shí)任務(wù)清除日志
>QuorumPeerConfig#isDistributed // 判斷是否為集群模式
>ServerCnxnFactory#createFactory() // 創(chuàng)建服務(wù)默認(rèn)為NIO,推薦netty
//***創(chuàng)建 初始化集群管理器**/
>QuorumPeerMain#getQuorumPeer
>QuorumPeer#setTxnFactory
>new FileTxnSnapLog // 數(shù)據(jù)文件管理器,用于檢測快照與日志文件
/** 初始化數(shù)據(jù)庫*/
>new ZKDatabase
>ZKDatabase#createDataTree //創(chuàng)建數(shù)據(jù)樹,所有的節(jié)點(diǎn)都會(huì)存儲(chǔ)在這
// 啟動(dòng)集群:同時(shí)啟動(dòng)線程
> QuorumPeer#start //
> QuorumPeer#loadDataBase // 從快照文件以及日志文件 加載節(jié)點(diǎn)并填充到dataTree中去
> QuorumPeer#startServerCnxnFactory // 啟動(dòng)netty 或java nio 服務(wù),對外開放2181 端口
> AdminServer#start// 啟動(dòng)管理服務(wù),netty http服務(wù),默認(rèn)端口是8080
> QuorumPeer#startLeaderElection // 開始執(zhí)行選舉流程
> quorumPeer.join() // 防止主進(jìn)程退出
流程說明:
- main方法啟動(dòng)
- 加載zoo.cfg 配置文件
- 解析配置
- 創(chuàng)建服務(wù)工廠
- 創(chuàng)建集群管理線程
- 設(shè)置數(shù)據(jù)庫文件管理器
- 設(shè)置數(shù)據(jù)庫
- ....設(shè)置設(shè)置
- start啟動(dòng)集群管理線程
- 加載數(shù)據(jù)節(jié)點(diǎn)至內(nèi)存
- 啟動(dòng)netty 服務(wù),對客戶端開放端口
- 啟動(dòng)管理員Http服務(wù),默認(rèn)8080端口
- 啟動(dòng)選舉流程
- join 管理線程,防止main 進(jìn)程退出
netty 服務(wù)啟動(dòng)流程
服務(wù)UML類圖

設(shè)置netty啟動(dòng)參數(shù)
-Dzookeeper.serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory
初始化:
關(guān)鍵代碼:
#初始化管道流
#channelHandler 是一個(gè)內(nèi)部類是具體的消息處理器。protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); if (secure) {
initSSL(pipeline); } pipeline.addLast("servercnxnfactory", channelHandler);
}
channelHandler 類結(jié)構(gòu)

執(zhí)行堆棧:
NettyServerCnxnFactory#NettyServerCnxnFactory // 初始化netty服務(wù)工廠
> NettyUtils.newNioOrEpollEventLoopGroup // 創(chuàng)建IO線程組
> NettyUtils#newNioOrEpollEventLoopGroup() // 創(chuàng)建工作線程組
>ServerBootstrap#childHandler(io.netty.channel.ChannelHandler) // 添加管道流
>NettyServerCnxnFactory#start // 綁定端口,并啟動(dòng)netty服務(wù)
創(chuàng)建連接:
每當(dāng)有客戶端新連接進(jìn)來,就會(huì)進(jìn)入該方法 創(chuàng)建 NettyServerCnxn對象。并添加至cnxns隊(duì)列
執(zhí)行堆棧
CnxnChannelHandler#channelActive
>new NettyServerCnxn // 構(gòu)建連接器
>NettyServerCnxnFactory#addCnxn // 添加至連接器,并根據(jù)客戶端IP進(jìn)行分組
>ipMap.get(addr) // 基于IP進(jìn)行分組
讀取消息:
執(zhí)行堆棧
CnxnChannelHandler#channelRead
>NettyServerCnxn#processMessage // 處理消息
>NettyServerCnxn#receiveMessage // 接收消息
>ZooKeeperServer#processPacket //處理消息包
>org.apache.zookeeper.server.Request // 封裝request 對象
>org.apache.zookeeper.server.ZooKeeperServer#submitRequest // 提交request
>org.apache.zookeeper.server.RequestProcessor#processRequest // 處理請求
快照與事務(wù)日志存儲(chǔ)結(jié)構(gòu)
概要
ZK中所有的數(shù)據(jù)都是存儲(chǔ)在內(nèi)存中,即zkDataBase中。但同時(shí)所有對ZK數(shù)據(jù)的變更都會(huì)記錄到事務(wù)日志中,并且當(dāng)寫入到一定的次數(shù)就會(huì)進(jìn)行一次快照的生成。已保證數(shù)據(jù)的備份。其后綴就是ZXID(唯一事務(wù)ID)。
- 事務(wù)日志:每次增刪改,的記錄日志都會(huì)保存在文件當(dāng)中
- 快照日志:存儲(chǔ)了在指定時(shí)間節(jié)點(diǎn)下的所有的數(shù)據(jù)
存儲(chǔ)結(jié)構(gòu)
zkDdataBase 是zk數(shù)據(jù)庫基類,所有節(jié)點(diǎn)都會(huì)保存在該類當(dāng)中,而對Zk進(jìn)行任何的數(shù)據(jù)變更都會(huì)基于該類進(jìn)行。zk數(shù)據(jù)的存儲(chǔ)是通過DataTree 對象進(jìn)行,其用了一個(gè)map 來進(jìn)行存儲(chǔ)。

UML 類圖:

讀取快照日志:
org.apache.zookeeper.server.SnapshotFormatter
讀取事務(wù)日志:
org.apache.zookeeper.server.LogFormatter
快照相關(guān)配置

快照裝載流程
>ZooKeeperServer#loadData // 加載數(shù)據(jù)
>FileTxnSnapLog#restore // 恢復(fù)數(shù)據(jù)
>FileSnap#deserialize() // 反序列化數(shù)據(jù)
>FileSnap#findNValidSnapshots // 查找有效的快照
>Util#sortDataDir // 基于后綴排序文件