Apache ZooKeeper - 选举Leader源码流程深度解析
文章目錄
- 流程圖
- Round
- Leader 選舉
- 服務啟動時的 Leader 選舉
- 發起投票
- 接收投票
- 統計投票
- Leader 崩潰觸發的 Leader 選舉
- 變更服務器狀態
- 發起投票
- 接收投票
- 統計投票
- 源碼分析
流程圖
Round
Leader 選舉
Leader 服務器的作用是管理 ZooKeeper 集群中的其他服務器。
因此,如果是單獨一臺服務器,不構成集群規模。在 ZooKeeper 服務的運行中不會選舉 Leader 服務器,也不會作為 Leader 服務器運行
我們知道一個 ZooKeeper 服務要想滿足集群方式運行,至少需要三臺服務器。這里我們就以三臺服務器組成的 ZooKeeper 集群為例,介紹一下 Leader 服務器選舉的內部過程和底層實現。
Leader 服務器的選舉操作主要發生在兩種情況下:
- 第一種就是 ZooKeeper 集群服務啟動的時候
- 第二種就是在 ZooKeeper 集群中舊的 Leader 服務器失效時,這時 ZooKeeper 集群需要選舉出新的 Leader 服務器
服務啟動時的 Leader 選舉
我們先來看下在 ZooKeeper 集群服務最初啟動的時候,Leader 服務器是如何選舉的。
在 ZooKeeper 集群啟動時,需要在集群中的服務器之間確定一臺 Leader 服務器。
當 ZooKeeper 集群中的三臺服務器啟動之后,首先會進行通信檢查,如果集群中的服務器之間能夠進行通信。集群中的三臺機器開始嘗試尋找集群中的 Leader 服務器并進行數據同步等操作。
如果這時沒有搜索到 Leader 服務器,說明集群中不存在 Leader 服務器。這時 ZooKeeper 集群開始發起 Leader 服務器選舉。
在整個 ZooKeeper 集群中 Leader 選舉主要可以分為三大步驟分別是:發起投票、接收投票、統計投票。
發起投票
在 ZooKeeper 服務器集群初始化啟動的時候,集群中的每一臺服務器都會將自己作為 Leader 服務器進行投票。也就是每次投票時,發送的服務器的 myid(服務器標識符)和 ZXID (集群投票信息標識符)等選票信息字段都指向本機服務器。
而一個投票信息就是通過這兩個字段組成的。以集群中三個服務器 Serverhost1、Serverhost2、Serverhost3 為例,三個服務器的投票內容分別是:Severhost1 的投票是(1,0)、Serverhost2 服務器的投票是(2,0)、Serverhost3 服務器的投票是(3,0)。
接收投票
集群中各個服務器在發起投票的同時,也通過網絡接收來自集群中其他服務器的投票信息。
在接收到網絡中的投票信息后,服務器內部首先會判斷該條投票信息的有效性。檢查該條投票信息的時效性,是否是本輪最新的投票,并檢查該條投票信息是否是處于 LOOKING 狀態的服務器發出的。
統計投票
在接收到投票后,ZooKeeper 集群就該處理和統計投票結果了。
對于每條接收到的投票信息,集群中的每一臺服務器都會將自己的投票信息與其接收到的 ZooKeeper 集群中的其他投票信息進行對比。
主要進行對比的內容是 ZXID,ZXID 數值比較大的投票信息優先作為 Leader 服務器。如果每個投票信息中的 ZXID 相同,就會接著比對投票信息中的 myid 信息字段,選舉出 myid 較大的服務器作為 Leader 服務器。
拿上面列舉的三個服務器組成的集群例子來說,對于 Serverhost1,服務器的投票信息是(1,0),該服務器接收到的 Serverhost2 服務器的投票信息是(2,0)。在 ZooKeeper 集群服務運行的過程中,首先會對比 ZXID,發現結果相同之后,對比 myid,發現 Serverhost2 服務器的 myid 比較大,于是更新自己的投票信息為(2,0),并重新向 ZooKeeper 集群中的服務器發送新的投票信息。
而 Serverhost2 服務器則保留自身的投票信息,并重新向 ZooKeeper 集群服務器中發送投票信息。
而當每輪投票過后,ZooKeeper 服務都會統計集群中服務器的投票結果,判斷是否有過半數的機器投出一樣的信息。如果存在過半數投票信息指向的服務器,那么該臺服務器就被選舉為 Leader 服務器。
比如上面的例子中,ZooKeeper 集群會選舉 Severhost2 服務器作為 Leader 服務器。
當 ZooKeeper 集群選舉出 Leader 服務器后,ZooKeeper 集群中的服務器就開始更新自己的角色信息,除被選舉成 Leader 的服務器之外,其他集群中的服務器角色變更為 Following。
Leader 崩潰觸發的 Leader 選舉
接下來我們再看一下在 ZooKeeper 集群服務的運行過程中,Leader 服務器是如果進行選舉的。
在 ZooKeeper 集群服務的運行過程中,Leader 服務器作為處理事物性請求以及管理其他角色服務器,在 ZooKeeper 集群中起到關鍵的作用。
當 ZooKeeper 集群中的 Leader 服務器發生崩潰時,集群會暫停處理事務性的會話請求,直到 ZooKeeper 集群中選舉出新的 Leader 服務器。
而整個 ZooKeeper 集群在重新選舉 Leader 時也經過了四個過程,分別是變更服務器狀態、發起投票、接收投票、統計投票。
其中,與初始化啟動時 Leader 服務器的選舉過程相比,變更狀態和發起投票這兩個階段的實現是不同的。下面我們來分別看看這兩個階段。
變更服務器狀態
與ZooKeeper 集群服務器初始化階段不同, 在 ZooKeeper 集群服務運行的過程中,集群中每臺服務器的角色已經確定了,當 Leader 服務器崩潰后 ,ZooKeeper 集群中的其他服務器會首先將自身的狀態信息變為 LOOKING 狀態,該狀態表示服務器已經做好選舉新 Leader 服務器的準備了,這之后整個 ZooKeeper 集群開始進入選舉新的 Leader 服務器過程。
發起投票
ZooKeeper 集群重新選舉 Leader 服務器的過程中發起投票的過程與初始化啟動時發起投票的過程基本相同。首先每個集群中的服務器都會投票給自己,將投票信息中的 Zxid 和 myid 分別指向本機服務器。
接收投票
同服務啟動時的 Leader 選舉的 接收投票步驟
統計投票
同服務啟動時的 Leader 選舉的 統計投票步驟
源碼分析
ZooKeeper 中實現的選舉算法有三種,而在目前的 ZooKeeper 3.6 版本后,只支持 “快速選舉” 這一種算法。
而在代碼層面的實現中,QuorumCnxManager 作為核心的實現類,用來管理 Leader 服務器與 Follow 服務器的 TCP 通信,以及消息的接收與發送等功能。在 QuorumCnxManager 中,主要定義了 ConcurrentHashMap<Long, SendWorker> 類型的 senderWorkerMap 數據字段,用來管理每一個通信的服務器。
public class QuorumCnxManager {final ConcurrentHashMap<Long, SendWorker> senderWorkerMap;final ConcurrentHashMap<Long, ArrayBlockingQueue<ByteBuffer>> queueSendMap;final ConcurrentHashMap<Long, ByteBuffer> lastMessageSent;}而在 QuorumCnxManager 類的內部,定義了 RecvWorker 內部類。該類繼承了一個 ZooKeeperThread 類的多線程類。主要負責消息接收。在 ZooKeeper 的實現中,為每一個集群中的通信服務器都分配一個 RecvWorker,負責接收來自其他服務器發送的信息。在 RecvWorker 的 run 函數中,不斷通過 queueSendMap 隊列讀取信息。
class SendWorker extends ZooKeeperThread {Long sid;Socket sock;volatile boolean running = true;DataInputStream din;final SendWorker sw;public void run() {threadCnt.incrementAndGet();while (running && !shutdown && sock != null) {int length = din.readInt();if (length <= 0 || length > PACKETMAXSIZE) {throw new IOException("Received packet with invalid packet: "+ length);}byte[] msgArray = new byte[length];din.readFully(msgArray, 0, length);ByteBuffer message = ByteBuffer.wrap(msgArray);addToRecvQueue(new Message(message.duplicate(), sid));}}}除了接收信息的功能外,QuorumCnxManager 內還定義了一個 SendWorker 內部類用來向集群中的其他服務器發送投票信息。如下面的代碼所示。在 SendWorker 類中,不會立刻將投票信息發送到 ZooKeeper 集群中,而是將投票信息首先插入到 pollSendQueue 隊列,之后通過 send 函數進行發送。
class SendWorker extends ZooKeeperThread {Long sid;Socket sock;RecvWorker recvWorker;volatile boolean running = true;DataOutputStream dout;public void run() {while (running && !shutdown && sock != null) {ByteBuffer b = null;try {ArrayBlockingQueue<ByteBuffer> bq = queueSendMap.get(sid);if (bq != null) {b = pollSendQueue(bq, 1000, TimeUnit.MILLISECONDS);} else {LOG.error("No queue of incoming messages for " +"server " + sid);break;}if(b != null){lastMessageSent.put(sid, b);send(b);}} catch (InterruptedException e) {LOG.warn("Interrupted while waiting for message on queue",e);}}}}實現了投票信息的發送與接收后,接下來我們就來看看如何處理投票結果。
在 ZooKeeper 的底層,是通過 FastLeaderElection 類實現的。如下面的代碼所示,在 FastLeaderElection 的內部,定義了最大通信間隔 maxNotificationInterval、服務器等待時間 finalizeWait 等屬性配置
public class FastLeaderElection implements Election {final static int maxNotificationInterval = 60000;final static int IGNOREVALUE = -1QuorumCnxManager manager;}在 ZooKeeper 底層通過 getVote 函數來設置本機的投票內容,如下面的代碼所示,在 getVote 中通過 proposedLeader 服務器信息、proposedZxid 服務器 ZXID、proposedEpoch 投票輪次等信息封裝投票信息。
synchronized public Vote getVote(){return new Vote(proposedLeader, proposedZxid, proposedEpoch);}在完成投票信息的封裝以及投票信息的接收和發送后。一個 ZooKeeper 集群中,Leader 服務器選舉底層實現的關鍵步驟就已經介紹完了。 Leader 節點的底層實現過程的邏輯相基本分為封裝投票信息、發送投票、接收投票等。
完了 走了
總結
以上是生活随笔為你收集整理的Apache ZooKeeper - 选举Leader源码流程深度解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache ZooKeeper - L
- 下一篇: leetcode - two-sum