ZooKeeper系列文章:ZooKeeper 源碼和實踐揭秘(二)


導語
ZooKeeper 是個針對大型分布式系統(tǒng)的高可用、高性能且具有一致性的開源協(xié)調服務,被廣泛的使用。對于開發(fā)人員,ZooKeeper 是一個學習和實踐分布式組件的不錯的選擇。本文對 ZooKeeper 的源碼進行簡析,也會介紹 ZooKeeper 實踐經驗,希望能幫助到 ZooKeeper 初學者?。文章部分內容參考了一些網絡文章,已標注在末尾參考文獻中。

ZooKeeper簡介
1. 初衷
在業(yè)務中使用了 ZooKeeper 作為消息系統(tǒng),在開發(fā)和運維過程中,也遇到一些問題,萌發(fā)了閱讀源碼窺視實現(xiàn)細節(jié)的想法。同時我們運維的 ZooKeeper 集群規(guī)模和數(shù)據(jù)規(guī)模非常大,也想把運維的經驗分享出來供參考去規(guī)避風險點和性能調優(yōu)。
2.目標讀者
本文是介紹 ZooKeeper 基礎知識和源碼分析的入門級材料,適合用于初步進入分布式系統(tǒng)的開發(fā)人員,以及使用 ZooKeeper 進行生產經營的應用程序運維人員。
Zookeeper系列文章介紹
第 1 篇:主要介紹 ZooKeeper 使命、地位、基礎的概念和基本組成模塊,以及 ZooKeeper 內部運行原理,此部分主要從書籍《ZooKeeper 分布式過程協(xié)同技術詳解》摘錄,對于有 ZooKeeper 基礎的可以略過。堅持主要目的,不先陷入解析源碼的繁瑣的實現(xiàn)上,而是從系統(tǒng)和底層看 ZooKeeper 如何運行,通過從高層次介紹其所使用的協(xié)議,以及 ZooKeeper 所采用的在提高性能的同時還具備容錯能力的機制。
第 2 章節(jié):簡析 ZooKeeper 的源碼實現(xiàn),主要目的去介紹 ZooKeeper 集群的工作流程,給出看源碼的簡要指引,能更快上手去深入閱讀源碼
第 3 章節(jié):主要介紹業(yè)務用 zookeeper 做消息系統(tǒng)的實踐,在實踐中的優(yōu)化點和踩坑的地方,由于業(yè)務場景和規(guī)模的差別,關注點和優(yōu)化點也差別很大,也歡迎在評論區(qū)更新使用 ZooKeeper 共性問題。
在大數(shù)據(jù)和云計算盛行的今天,應用服務由很多個獨立的程序組成,這些獨立的程序則運行在形形色色,千變萬化的一組計算機上,而如何讓一個應用中的多個獨立的程序協(xié)同工作是一件非常困難的事情。而 ZooKeeper 就是一個分布式的,開放源碼的分布式應用程序協(xié)調服務。它使得應用開發(fā)人員可以更多的關注應用本身的邏輯,而不是協(xié)同工作上。從系統(tǒng)設計看,ZooKeeper 從文件系統(tǒng) API 得到啟發(fā),提供一組簡單的 API,使得開發(fā)人員可以實現(xiàn)通用的協(xié)作任務,例如選舉主節(jié)點,管理組內成員的關系,管理元數(shù)據(jù)等,同時 ZooKeeper 的服務組件運行在一組專用的服務器之上,也保證了高容錯性和可擴展性。
本章節(jié)是Zookeeper系列文章的第二篇,本文將為大家解析Zookeeper源碼,幫助大家更好的理解源碼。

ZooKeeper源碼解析
以 3.5.5 版本作為分析。主要從服務端,客戶端,以及服務端和客戶端結合的部分分析源碼。在分析源碼時,主要從數(shù)據(jù)結構,類結構,線程模型,流程等方面看。(注:本章節(jié)參考了網上 ZooKeeper 的分析文章,借用了不少文字描述。)
服務端
ZooKeeper 服務的啟動方式分為三種,即單機模式、偽分布式模式、分布式模式。本章節(jié)主要研究分布式模式的啟動模型,其主要要經過 Leader 選舉,集群數(shù)據(jù)同步,啟動服務器。
- 解析 config 文件;
- 數(shù)據(jù)恢復;
- 監(jiān)聽 client 連接(但還不能處理請求);
- bind 選舉端口監(jiān)聽 server 連接;
- 選舉;
- 初始化 ZooKeeperServer;
- 數(shù)據(jù)同步;
同步結束,啟動 client 請求處理能力。

服務端啟動流程(分布式模式)
注:本章節(jié)主要是參考網上 blog 文章,對部分內容作了調整與處理。
具體細節(jié)如下,
1. ZooKeeper 啟動類是 QuorumPeerMain,是將配置文件通過參數(shù)方式傳入。

2. DatadirCleanupManager 線程,由于 ZooKeeper 的任何一個變更操作都產生事務,事務日志需要持久化到硬盤,同時當寫操作達到一定量或者一定時間間隔后,會對內存中的數(shù)據(jù)進行一次快照并寫入到硬盤上的 snapshop 中,快照為了縮短啟動時加載數(shù)據(jù)的時間從而加快整個系統(tǒng)啟動。而隨著運行時間的增長生成的 transaction log 和 snapshot 將越來越多,所以要定期清理,DatadirCleanupManager 就是啟動一個 TimeTask 定時任務用于清理 DataDir 中的 snapshot 及對應的 transaction log。

3. 根據(jù)配置中的 servers 數(shù)量判斷是集群環(huán)境還是單機環(huán)境,如果單機環(huán)境以 standalone 模式運行直接調用 ZooKeeperServerMain.main()方法,否則進入集群模式中。
4. 創(chuàng)建 ServerCnxnFactory 實例, ServerCnxnFactory 從名字就可以看出其是一個工廠類,負責管理 ServerCnxn,ServerCnxn 這個類代表了一個客戶端與一個 server 的連接,每個客戶端連接過來都會被封裝成一個 ServerCnxn 實例用來維護了服務器與客戶端之間的 Socket 通道。
5. QuorumPeer.start()是 ZooKeeper 中非常重要的一個方法入口,

- loadDataBase:涉及到的核心類是 ZKDatabase,并借助于 FileTxnSnapLog 工具類將 snap 和 transaction log 反序列化到內存中,最終構建出內存數(shù)據(jù)結構 DataTree。
- cnxnFactory.start:之前介紹過 ServerCnxnFactory 作用,ServerCnxnFactory 本身也可以作為一個線程。
startLeaderElection():這個主要是初始化一些 Leader 選舉工作。


Leader 選舉涉及到節(jié)點間的網絡 IO,QuorumCnxManager 就是負責集群中各節(jié)點的網絡 IO,QuorumCnxManager 包含一個內部類 Listener,Listener 是一個線程,這里啟動 Listener 線程,主要啟動選舉監(jiān)聽端口并處理連接進來的 Socket;FastLeaderElection 就是封裝了具體選舉算法的實現(xiàn)。
?4. super.start():QuorumPeer 本身也是一個線程,其繼承了 Thread 類,這里就是啟動 QuorumPeer 線程,就是執(zhí)行 QuorumPeer.run 方法。
QuorumPeer 線程進入到一個無限循環(huán)模式,不停的通過 getPeerState 方法獲取當前節(jié)點狀態(tài),然后執(zhí)行相應的分支邏輯。大致流程可以簡單描述如下:
- 首先系統(tǒng)剛啟動時 serverState 默認是 LOOKING,表示需要進行 Leader 選舉,這時進入 Leader 選舉狀態(tài)中,會調用 FastLeaderElection.lookForLeader 方法,lookForLeader 方法內部也包含了一個循環(huán)邏輯,直到選舉出 Leader 才會跳出 lookForLeader 方法,如果選舉出的 Leader 就是本節(jié)點,則將 serverState=LEADING 賦值,否則設置成 FOLLOWING 或 OBSERVING。
然后 QuorumPeer.run 進行下一輪次循環(huán),通過 getPeerState 獲取當前 serverState 狀態(tài),如果是 LEADING,則表示當前節(jié)點當選為 LEADER,則進入 Leader 角色分支流程,執(zhí)行作為一個 Leader 該干的任務;如果是 FOLLOWING 或 OBSERVING,則進入 Follower 或 Observer 角色,并執(zhí)行其相應的任務。注意:進入分支路程會一直阻塞在其分支中,直到角色轉變才會重新進行下一輪次循環(huán),比如 Follower 監(jiān)控到無法與 Leader 保持通信了,會將 serverState 賦值為 LOOKING,跳出分支并進行下一輪次循環(huán),這時就會進入 LOOKING 分支中重新進行 Leader 選舉。

服務器各階段
數(shù)據(jù)恢復
在服務器啟動階段需要進行數(shù)據(jù)恢復階段。

?Leader 選舉
Leader 選舉初始化 QuorumPeer.startLeaderElection(),Leader 選舉涉及到兩個核心類:QuorumCnxManager 和 FastLeaderElection。




在 createElectionAlgorithm()算法中,創(chuàng)建一個 QuorumCnxManager 實例,啟動 QuorumCnxManager.Listener 線程,構建選舉算法 FastLeaderElection,然后相互交互投票信息,進入 Leader 選舉過程。

QuorumCnxManager 有一個內部類 Listener,初始化一個 ServerSocket,然后在一個 while 循環(huán)中調用 accept 接收客戶端(注意:這里的客戶端指的是集群中其它服務器)連接。當有客戶端連接進來后,會將該客戶端 Socket 封裝成 RecvWorker 和 SendWorker,它們都是線程,分別負責和該 Socket 所代表的客戶端進行讀寫。其中,RecvWorker 和 SendWorker 是成對出現(xiàn)的,每對負責維護和集群中的一臺服務器進行網絡 IO 通信。
FastLeaderElection 負責 Leader 選舉核心規(guī)則算法實現(xiàn),包含了兩個內部類 WorkerSender 和 WorkerReceiver 線程。
- 將數(shù)據(jù)封裝成 ToSend 格式放入到 sendqueue;
- WorkerSender 線程會一直輪詢提取 sendqueue 中的數(shù)據(jù),當提取到 ToSend 數(shù)據(jù)后,會獲取到集群中所有參與 Leader 選舉節(jié)點(除 Observer 節(jié)點外的節(jié)點)的 sid,如果 sid 即為本機節(jié)點,則轉成 Notification 直接放入到 recvqueue 中,因為本機不再需要走網絡 IO;否則放入到 queueSendMap 中,key 是要發(fā)送給哪個服務器節(jié)點的 sid,ByteBuffer 即為 ToSend 的內容,queueSendMap 維護的著當前節(jié)點要發(fā)送的網絡數(shù)據(jù)信息,由于發(fā)送到同一個 sid 服務器可能存在多條數(shù)據(jù),所以 queueSendMap 的 value 是一個 queue 類型;
- QuorumCnxManager 中的 SendWorkder 線程不停輪詢 queueSendMap 中是否存在自己要發(fā)送的數(shù)據(jù),每個 SendWorkder 線程都會綁定一個 sid 用于標記該 SendWorkder 線程和哪個對端服務器進行通信,因此,queueSendMap.get(sid)即可獲取該線程要發(fā)送數(shù)據(jù)的 queue,然后通過 queue.poll()即可提取該線程要發(fā)送的數(shù)據(jù)內容;
然后通過調用 SendWorkder 內部維護的 socket 輸出流即可將數(shù)據(jù)寫入到對端服務器。
- QuorumCnxManager 中的 RecvWorker 線程會一直從 Socket 的輸入流中讀取數(shù)據(jù),當讀取到對端發(fā)送過來的數(shù)據(jù)時,轉成 Message 格式并放入到 recvQueue 中;
- FastLeaderElection.WorkerReceiver 線程會輪詢方式從 recvQueue 提取數(shù)據(jù)并轉成 Notification 格式放入到 recvqueue 中;
FastLeaderElection 從 recvqueu 提取所有的投票信息進行比較 最終選出一個 Leader。
Leader 選舉算法實現(xiàn)
上面已經介紹了 Leader 選舉期間網絡 IO 的大致流程,下面介紹下具體選舉算法如何實現(xiàn)。


QuorumPeer 線程中會有一個 Loop 循環(huán),獲取 serverState 狀態(tài)后進入不同分支,當分支退出后繼續(xù)下次循環(huán),F(xiàn)astLeaderElection 選舉策略調用就是發(fā)生在檢測到 serverState 狀態(tài)為 LOOKING 時進入到 LOOKING 分支中調用的。
進入到 LOOKING 分支執(zhí)行的代碼邏輯:
setCurrentVote(makeLEStrategy().lookForLeader());從上面代碼可以看出,Leader 選舉策略入口方法為:FastLeaderElection.lookForLeader()方法。當 QuorumPeer.serverState 變成 LOOKING 時,該方法會被調用,表示執(zhí)行新一輪 Leader 選舉。下面來看下 lookForLeader 方法的大致實現(xiàn)邏輯:
更新自己期望投票信息,即自己期望選哪個服務器作為 Leader(用 sid 代替期望服務器節(jié)點)以及該服務器 zxid、epoch 等信息,第一次投票默認都是投自己當選 Leader,然后調用 sendNotifications 方法廣播該投票到集群中所有可以參與投票服務器,廣播涉及到網絡 IO 流程前面已講解,這里就不再細說;
其中,updateProposal()方法有三個參數(shù):a.期望投票給哪個服務器(sid)、b.該服務器的 zxid、c.該服務器的 epoch,在后面會看到這三個參數(shù)是選舉 Leader 時的核心指標,后面再介紹。

首先對之前提到的選舉輪次 electionEpoch 進行判斷,這里分為三種情況:
- 只有對方發(fā)過來的投票的 electionEpoch 和當前節(jié)點相等表示是同一輪投票,即投票有效,然后調用 totalOrderPredicate()對投票進行 PK,返回 true 代表對端勝出,則表示第一次投票是錯誤的(第一次都是投給自己),更新自己投票期望對端為 Leader,然后調用 sendNotifications()將自己最新的投票廣播出去。返回 false 則代表自己勝出,第一次投票沒有問題,就不用管。
- 如果對端發(fā)過來的 electionEpoch 大于自己,則表明重置自己的 electionEpoch,然后清空之前獲取到的所有投票 recvset,因為之前獲取的投票輪次落后于當前則代表之前的投票已經無效了,然后調用 totalOrderPredicate()將當前期望的投票和對端投票進行 PK,用勝出者更新當前期望投票,然后調用 sendNotifications()將自己期望頭破廣播出去。注意:這里不管哪一方勝出,都需要廣播出去,而不是步驟 a 中己方勝出不需要廣播,這是因為由于 electionEpoch 落后導致之前發(fā)出的所有投票都是無效的,所以這里需要重新發(fā)送
- 如果對端發(fā)過來的 electionEpoch 小于自己,則表示對方投票無效,直接忽略不進行處理
totalOrderPredicate()實現(xiàn)了對投票進行 PK 規(guī)則:

下面簡單說下這個 PK 邏輯原理(勝出一方代表更有希望成為 Leader):
- 首先比較 epoch,哪個 epoch 哪個勝出,前面介紹過 epoch 代表了 Leader 的輪次,是一個遞增的,epoch 越大就意味著數(shù)據(jù)越新,Leader 數(shù)據(jù)越新則可以減少后續(xù)數(shù)據(jù)同步的效率,當然應該優(yōu)先選為 Leader;
- 然后才是比較 zxid,由于 zxid=epoch+counter,第一步已經把 epoch 比較過了,其實這步驟只是相當于比較 counter 大小,counter 越大則代表數(shù)據(jù)越新,優(yōu)先選為 Leader。注:其實第 1 和第 2 可以合并到一起,直接比較 zxid 即可,因為 zxid=epoch+counter,第 1 比較顯的有些多余;
- 如果前兩個指標都沒法比較出來,只能通過 sid 來確定,zxid 相等說明兩個服務器的數(shù)據(jù)是一致的,選擇 sid 大的當 Leader。

集群數(shù)據(jù)同步
Leader 選舉的流程,ZooKeeper 集群在 Leader 選舉完成后,集群中的各個節(jié)點就確定了自己的角色信息:Leader、Follower 或 Observer。

如上述代碼所述,節(jié)點確定了自己的角色后,就會進入自己的角色分支:對于 Leader 而言創(chuàng)建 Leader 實例并調用其 lead()函數(shù),對于 Follower 而言創(chuàng)建 Follower 實例并調用其 followLeader()函數(shù),對于 Observer 而言創(chuàng)建 Observer 實例并調用其 observeLeader()函數(shù)。在這三個函數(shù)中,服務器會進行相關的初始化并完成最終的啟動。
對于 Follower 和 Observer 而言,主要的初始化工作是要建立與 Leader 的連接并同步 epoch 信息,最后完成與 Leader 的數(shù)據(jù)同步。而 Leader 會啟動 LearnerCnxAcceptor 線程,該線程會接受來自 Follower 和 Observer(統(tǒng)稱為 Learner)的連接請求并為每個連接創(chuàng)建一個 LearnerHandler 線程,該線程會負責包括數(shù)據(jù)同步在內的與 learner 的一切通信。
Learn(Follower 或 Observer)節(jié)點會主動向 Leader 發(fā)起連接,ZooKeeper 就會進入集群同步階段,集群同步主要完成集群中各節(jié)點狀態(tài)信息和數(shù)據(jù)信息的一致。選出新的 Leader 后的流程大致分為:計算 epoch、統(tǒng)一 epoch、同步數(shù)據(jù)、廣播模式等四個階段。其中其前三個階段:計算 epoch、統(tǒng)一 epoch、同步數(shù)據(jù)就是這一節(jié)主要介紹的集群同步階段的主要內容,這三個階段主要完成新 Leader 與集群中的節(jié)點完成同步工作,處于這個階段的 zk 集群還沒有真正做好對外提供服務的能力,可以看著是新 leader 上任后進行的內部溝通、前期準備工作等,只有等這三個階段全部完成,新 leader 才會真正的成為 leader,這時 zk 集群會恢復正常可運行狀態(tài)并對外提供服務。
被選舉為 Leader 角色的節(jié)點,會創(chuàng)建一個 Leader 實例,然后執(zhí)行 Leader.lead()進入到 Leader 角色的任務分支中,其流程大致如下所示:


Leader 分支大致可以分為 5 個階段:啟動 LearnerCnxAcceptor 線程、計算 newEpoch、廣播 newEpoch、數(shù)據(jù)同步和集群狀態(tài)監(jiān)測。
Leader.lead()方法控制著 Leader 角色節(jié)點的主體流程,其實現(xiàn)較為簡單,大致模式都是通過阻塞方法阻塞當前線程,直到該階段完成 Leader 線程才會被喚醒繼續(xù)執(zhí)行下一個階段;而每個階段實現(xiàn)的具體細節(jié)及大量的網絡 IO 操作等都在 LearnerHandler 中實現(xiàn)。比如計算 newEpoch,Leader 中只會判斷 newEpoch 計算完成沒,沒有計算完成就會進入阻塞狀態(tài)掛起當前 Leader 線程,直到集群中一半以上的節(jié)點同步了 epoch 信息后 newEpoch 正式產生才會喚醒 Leader 線程繼續(xù)向下執(zhí)行;而計算 newEpoch 會涉及到 Leader 去收集集群中大部分 Learner 服務器的 epoch 信息,會涉及到大量的網絡 IO 通信等內容,這些細節(jié)部分都在 LearnerHandler 中實現(xiàn)。
涉及到網絡 IO 就會存在 Server 和 Client,這里的 Server 就是 Leader,Client 就是 Learner(Follower 和 Observer 統(tǒng)稱 Learner),對于 Server 端,主要關注 Leader 和 LearnerHandler 這兩個類,而對于 Client 端,根據(jù)角色分類主要關注 Follower 或 Observer 這兩個類。
ZooKeeper 中主要存在三個端口:
- 客戶端請求端口:對應于配置中的 clientPort,默認是 2181,就是客戶端連接 ZK 對其進行增刪改操作的端口;
- 集群選舉端口:之前分析過的集群中 Leader 選舉涉及到網絡 IO 使用的端口,對應于配置中“server.0=10.80.8.3:2888:2999”這里的 2999 就是集群選舉端口;
- 集群同步端口:Leader 選舉出后就會涉及到 Leader 和 Learner 之間的數(shù)據(jù)同步問題,集群同步端口的作用就是做這個使用的,對應于配置中”server.0=10.80.8.3:2888:2999“這里的 2888;
啟動 LearnerCnxAcceptor 線程
Leader 首先會啟動一個 LearnerCnxAcceptor 線程,這個線程做的工作就非常簡單了,就是不停的循環(huán) accept 接收 Learner 端的網絡請求(這里的監(jiān)聽端口就是上面說的同步監(jiān)聽端口,而不是選舉端口),Leader 選舉結束后被分配為 Follower 或 Observer 角色的節(jié)點會主動向 Leader 發(fā)起連接,Leader 端接收到一個網絡連接就會封裝成一個 LearnerHandler 線程。
Leader 類可以看成一個總管,和每個 Learner 服務器的交互任務都會被分派給 LearnerHandler 這個助手完成,當 Leader 檢測到一個任務被一半以上的 LearnerHandler 處理完成,即認為該階段結束,進入下一個階段。
計算 epoch
epoch 在 ZooKeeper 中是一個很重要的概念,前面也介紹過了:epoch 就相當于 Leader 的身份編號,就如同身份證編號一樣,每次選舉產生一個新 Leader 時,都會為該 Leader 重新計算出一個新 epoch。epoch 被設計成一個遞增值,比如上一個 Leader 的 epoch 是 1,假如重新選舉新的 Leader 就會被分配 epoch=1。
epoch 作用:可以防止舊 Leader 活過來后繼續(xù)廣播之前舊提議造成狀態(tài)不一致問題,只有當前 Leader 的提議才會被 Follower 處理。ZooKeeper 集群所有的事務請求操作都要提交由 Leader 服務器完成,Leader 服務器將事務請求轉成一個提議(Proposal)并分配一個事務 ID(zxid)后廣播給 Learner,zxid 就是由 epoch 和 counter(遞增)組成,當存在舊 leader 向 follower 發(fā)送命令的時候,follower 發(fā)現(xiàn) zxid 所在的 epoch 比當前的小,則直接拒絕,防止出現(xiàn)不一致性。
統(tǒng)一 epoch
newEpoch 計算完成后,該值只有 Leader 知道,現(xiàn)在需要將 newEpoch 廣播到集群中所有的服務器節(jié)點上,讓他們都更新下新 Leader 的 epoch 信息,這樣他們在處理請求時會根據(jù) epoch 判斷該請求是不是當前新 Leader 發(fā)出的,可以防止舊 Leader 活過來后繼續(xù)廣播之前舊提議造成狀態(tài)不一致問題,只有當前 Leader 的提議才會被 Follower 處理。
總結:廣播 newEpoch 流程也比較簡單,就是將之前計算出來的 newEpoch 封裝到 LEADERINFO 數(shù)據(jù)包中,然后廣播到集群中的所有節(jié)點,同時會收到 ACKEPOCH 回復數(shù)據(jù)包,當集群中一半以上的節(jié)點進行了回復則可以認為 newEpoch 廣播完成,則進入下一階段。同樣,為避免線程一直阻塞,休眠線程依然會被添加超時時間,超時后仍未完成則拋出 InterruptedException 異常重新進入 Leader 選舉狀態(tài)。
數(shù)據(jù)同步
之前分析過 Leader 的選舉策略:lastZxid 越大越會被優(yōu)先選為 Leader。lastZxid 是節(jié)點上最大的事務 ID,由于 zxid 是遞增的,lastZxid 越大,則表示該節(jié)點處理的數(shù)據(jù)越新,即數(shù)據(jù)越完整。所以,被選為 Leader 的節(jié)點數(shù)據(jù)完整性越高,為了數(shù)據(jù)一致性,這時就需要其它節(jié)點和 Leader 進行數(shù)據(jù)同步保持數(shù)據(jù)一致性。
- DIFF,learner 比 leader 少一些數(shù)據(jù);
- TRUNC,learner 數(shù)據(jù)比 leader 多;
- DIFF+TRUNC,learner 對 leader 多數(shù)據(jù)又少數(shù)據(jù);
SNAP,learner 比 leader 少很多數(shù)據(jù)。

服務角色
群首,追隨者,觀察者根本上都是服務器,在實現(xiàn)服務器主要抽象概念是請求處理器。請求處理器是對處理流水線上不同階段的抽象,每個服務器在初始化時實現(xiàn)一個請求處理器的序列。對于請求處理器,ZooKeeper 代碼里有一個叫 RequestProcessor 的接口,這個接口的主要方法是processRequest,它接受一個 Request 參數(shù),在一個請求處理器的流水線中,對于相鄰處理器的請求的處理是通過隊列實現(xiàn)解耦合。當一個處理器有一條請求需要下一個處理器進行處理時,它將這條請求加入隊列中。然后,它將處于等待狀態(tài)直到下一個處理器處理完此消息。本節(jié)主要看看各個服務器的請求處理器序列初始化和對隊列的使用與處理,處理器的細節(jié)可以參考源碼。

獨立服務器
獨立服務器請求鏈

獨立服務器是從 ZooKeeperServerMain.java 開始,

在 PrepRequestProcessor 中,消費請求隊列 submittedRequests,數(shù)據(jù)結構如下
LinkedBlockingQueuesubmittedRequests = new LinkedBlockingQueue();
PrepRequestProcessor 接受客戶端的請求并執(zhí)行這個請求,處理結果則是生成一個事務。不過只有改變 ZooKeeper 狀態(tài)的操作才會產生事務,對于讀操作并不會產生任何事務。



SyncRequestProcessor.java,SyncRequestProcessor 負責將事務持久化到磁盤上。實際上就是將事務數(shù)據(jù)按照順序追加到事務日志中,并形成快照數(shù)據(jù)。


FinalRequestProcessor.java,FinalRequestProcessor,如果 Request 對象包含事務數(shù)據(jù),該處理器就會接受對 ZooKeeper 數(shù)據(jù)樹的修改,否則,該處理器會從數(shù)據(jù)樹中讀取數(shù)據(jù)并返回客戶端。

群首服務器(Leader)
請求鏈




Follower

Observer

參考資料:https://www.jianshu.com/p/45f8a966fb47

One more thing
目前,騰訊云微服務引擎(Tencent Cloud Service Engine,簡稱TSE)已上線,并發(fā)布子產品服務注冊、配置中心(ZooKeeper/Nacos/Eureka/Apollo)、治理中心(PolarisMesh)。支持一鍵創(chuàng)建、免運維、高可用、開源增強的組件托管服務,歡迎點擊文末的「閱讀原文」了解詳情并使用!
TSE官網地址:
https://cloud.tencent.com/product/tse
參考文獻
- ZooKeeper-選舉實現(xiàn)分析:https://juejin.im/post/5cc2af405188252da4250047
- Apache ZooKeeper 官網:https://zookeeper.apache.org/
- ZooKeeper github:https://github.com/apache/zookeeper
- 《zookeeper-分布式過程協(xié)同技術詳解》【美】里德,【美】Flavio Junqueira 著
- ZooKeeper 源碼分析:https://blog.reactor.top/tags/Zookeeper/
- ZooKeeper-選舉實現(xiàn)分析:https://juejin.im/post/5cc2af405188252da4250047
- ZooKeeper 源碼分析:https://www.cnblogs.com/sunshine-2015/tag/zookeeper/
往期
推薦
《服務器又崩了?深度解析高可用架構的挑戰(zhàn)和實踐》
《Kratos技術系列|從Kratos設計看Go微服務工程實踐》
《Pulsar技術系列 - 深度解讀Pulsar Schema》
《Apache Pulsar事務機制原理解析|Apache Pulsar 技術系列》

掃描下方二維碼關注本公眾號,
了解更多微服務、消息隊列的相關信息!
解鎖超多鵝廠周邊!
戳原文,查看更多微服務引擎TSE信息!
點個在看你最好看
