一文搞懂Zookeeper原理

小猴子_X發表於2022-02-11
一.概述

 ZooKeeper 是什麼?

  • 是一個開源的分散式協調服務。使用分散式系統就無法避免對節點管理的問題(需要實時感知節點的狀態、對節點進行統一管理等等),而由於這些問題處理起來可能相對麻煩和提高了系統的複雜性,ZooKeeper作為一個能夠通用解決這些問題的中介軟體就應運而生了。
  • 從設計模式角度來理解:是一個基於觀察者模式設計的分散式服務管理框架,它負責儲存管理大家都關心的資料,一旦這些資料的狀態發生變化,Zookeeper 就 將負責通知已經在Zookeeper上註冊的那些觀察者做出相應的反應。
  • 實現原理:zookeeper=檔案系統+通知機制

Zookeeper的作用(應用場景)?

  • 統一配置管理:比如現在有A.yml,B.yml,C.yml配置檔案,裡面有一些公共的配置,但是如果後期對這些公共的配置進行修改,就需要修改每一個檔案,還要重啟伺服器。比較麻煩,現在將這些公共配置資訊放到ZK中,修改ZK的資訊,會通知A,B,C配置檔案。多方便
  • 統一命名服務:這個的理解其實跟域名一樣,在某一個節點下放一些ip地址,我現在只需要訪問ZK的一個Znode節點就可以獲取這些ip地址。
  • 同一叢集管理:分散式叢集中狀態的監控和管理,使用Zookeeper來儲存。
  • 分散式協調:這個是我們最常用的,比如把多個服務提供者的資訊放在某個節點上,服務的消費者就可以通過ZK呼叫。
    • 服務節點動態上下線:如何提供者當機,就會刪除在ZK的節點,然後ZK通知給消費者。
    • 軟負載均衡
    • 動態選舉Master:Zookeeper會每次選舉最小編號的作為Master,如果Master掛了,自然對應的Znode節點就會刪除。然後讓新的最小編號作為Master,這樣就可以實現動態選舉的功能了。
  • 分散式鎖(後續出文章講)
二.原理

之所以能做上述功能,主要是歸功於ZK的檔案系統通知機制。下面我們來分析這兩個機制


 檔案系統:

ZooKeeper的資料結構,跟Unix檔案系統非常類似,可以看做是一顆,每個節點叫做Znode。每一個Znode只能存1MB資料。資料只是配置資訊。每一個節點可以通過路徑來標識,結構圖如下:

 Znode節點主要有4中型別:

  • 臨時目錄節點:客戶端與Zookeeper斷開連線後,該節點被刪除
  • 臨時順序編號目錄節點:基本特性同臨時節點,只是增加了順序屬性,節點名後邊會追加一個由父節點維護的自增整型數字。
  • 持久化目錄節點:客戶端與Zookeeper斷開連線後,該節點依舊存在
  • 持久化順序編號目錄節點:基本特性同持久節點,只是增加了順序屬性,節點名後邊會追加一個由父節點維護的自增整型數字。

 通知機制 (監聽機制)

Zookeeper可以提供分散式資料的釋出/訂閱功能,依賴的就是Wather監聽機制。

客戶端可以向服務端註冊Wather監聽,服務端的指定事件觸發之後,就會向客戶端傳送一個事件通知。具體步如下:

  1. 客戶端向服務端註冊Wather監聽
  2. 儲存Wather物件到客戶端本地的WatherManager中
  3. 服務端Wather事件觸發後,客戶端收到服務端通知,從WatherManager(watcher管理器)中取出對應Wather物件執行回撥邏輯

 主要監聽2方面內容:

  • 監聽Znode節點的資料變化:就是那個節點資訊更新了。

  • 監聽子節點的增減變化:就是增加了一個Znode或者刪除了一個Znode。

幾個特性:

  • 一次性:一旦一個Wather觸發之後,Zookeeper就會將它從儲存中移除
  • 客戶端序列:客戶端的Wather回撥處理是序列同步的過程,不要因為一個Wather的邏輯阻塞整個客戶端
  • 輕量:Wather通知的單位是WathedEvent,只包含通知狀態、事件型別和節點路徑,不包含具體的事件內容,具體的時間內容需要客戶端主動去重新獲取資料
三.ZK叢集(相關概念)

  • Leader:負責寫資料。(寫資料都有事務)
  • Follower:負責讀資料,節點的選舉過半寫成功。(讀資料沒有事務)
  • Observer:只負責讀。

從上面的角色種,我們可以總結ZK節點的工作狀態(服務狀態)

  • LOOKING:尋 找 Leader 狀態。當伺服器處於該狀態時,它會認為當前叢集中沒有 Leader,因此需要進入 Leader 選舉狀態。
  • FOLLOWING:跟隨者狀態。表明當前伺服器角色是 Follower。
  • LEADING:領導者狀態。表明當前伺服器角色是 Leader。
  • OBSERVING:觀察者狀態。表明當前伺服器角色是 Observer。

其他概念:

  • zxid:全域性事務ID,分為兩部分:
    • 紀元(epoch)部分:epoch代表當前叢集所屬的哪個leader,leader的選舉就類似一個朝代的更替,你前朝的劍不能斬本朝的官,用epoch代表當前命令的有效性。
    • 計數器(counter)部分,是一個全域性有序的數字,是一個遞增的數字。

寫資料原理:

  • 寫給leader,leader再通知其他節點
  • 寫給follower,follower沒有寫的許可權,交給leader寫,leader再通知。
  • 半數機制:比如上圖,zookeeper在通知其他節點寫的時候,達到半數就通知客戶端寫完成。 不需要全部寫完成。所以叢集的數量一般是奇數。
三.ZK叢集(原理)

 上面我們知道叢集的基本概念,那麼也會引出很多問題:ZK怎麼保證資料一致性?Leader當機瞭如何進行選舉?選舉後資料如何同步?


 ZK怎麼保證資料一致性?

由於ZK只有Leader節點可以寫入資料,如果是其他節點收到寫入資料的請求,則會將之轉發給Leader節點。ZK通過ZAB協議來實現資料的最終順序一致性,他是一個類似2PC兩階段提交的過程。ZAB有2種模式:訊息廣播崩潰恢復(選舉)。

 一般我們正常是訊息廣播:

  • 第一階段:廣播事務階段:對應圖上的1,2
    • Leader收到請求之後,將它轉換為一個proposal提議,並且為每個提議分配一個事務ID:zxid,然後把提議放入到一個FIFO的佇列中,按照FIFO的策略傳送給所有的Follower。
    • Follower收到提議之後,以事務日誌的形式寫入到本地磁碟中,寫入成功後返回ACK給Leader
  • 第二階段:廣播提交操作:對應圖上的3
    • Leader在收到超過半數的Follower的ACK之後,即可認為資料寫入成功,就會傳送commit命令給Follower告訴他們可以提交proposal了。

Leader當機瞭如何進行選舉?

這就得使用ZAB的第二種模式,崩潰恢復模式:


選舉後資料如何同步?

那實際上Zookeeper在選舉之後,Follower和Observer(統稱為Learner)就會去向Leader註冊,然後就會開始資料同步的過程。

資料同步包含3個主要值和4種形式。

  • PeerLastZxid:Learner伺服器最後處理的ZXID
  • minCommittedLog:Leader提議快取佇列中最小ZXID
  • maxCommittedLog:Leader提議快取佇列中最大ZXID

同步策略:

  • 直接差異化同步 (DIFF同步):如果PeerLastZxid在minCommittedLog和maxCommittedLog之間,那麼則說明Learner伺服器還沒有完全同步最新的資料。
    1. 首先Leader向Learner傳送DIFF指令,代表開始差異化同步,然後把差異資料(從PeerLastZxid到maxCommittedLog之間的資料)提議proposal傳送給Learner
    2. 傳送完成之後傳送一個NEWLEADER命令給Learner,同時Learner返回ACK表示已經完成了同步
    3. 接著等待叢集中過半的Learner響應了ACK之後,就傳送一個UPTODATE命令,Learner返回ACK,同步流程結束
  • 先回滾再差異化同步(Trunc+DIFF同步):特殊場景:如果Leader剛生成一個proposal,還沒有來得及傳送出去,此時Leader當機,重新選舉之後作為Follower,但是新的Leader沒有這個proposal資料
    • 舉個例子:假設現在的Leader是A,minCommittedLog=1,maxCommittedLog=3,剛好生成的一個proposal的ZXID=4,然後掛了。重新選舉出來的Leader是B,B之後又處理了2個提議,然後minCommittedLog=1,maxCommittedLog=5。這時候A的PeerLastZxid=4,在(1,5)之間。那麼這一條只存在於A的提議怎麼處理?
    • A要進行事務回滾,相當於拋棄這條資料,並且回滾到最接近於PeerLastZxid的事務,對於A來說,也就是PeerLastZxid=3。流程和DIFF一致,只是會先傳送一個TRUNC命令,然後再執行差異化DIFF同步。

  • 僅回滾同步(TRUNC同步):
    • 針對PeerLastZxid大於maxCommittedLog的場景,流程和上述一致,事務將會被回滾到maxCommittedLog的記錄。
    • 這個其實就更簡單了,也就是你可以認為TRUNC+DIFF中的例子,新的Leader B沒有處理提議,所以B中minCommittedLog=1,maxCommittedLog=3。
    • 所以A的PeerLastZxid=4就會大於maxCommittedLog了,也就是A只需要回滾就行了,不需要執行差異化同步DIFF了。
  • 全量同步 (SNAP同步):
    • 適用於兩個場景:

      1. PeerLastZxid小於minCommittedLog
      2. Leader伺服器上沒有提議快取佇列,並且PeerLastZxid不等於Leader的最大ZXID
    • 這兩種場景下,Leader將會傳送SNAP命令,把全量的資料都傳送給Learner進行同步。

有可能會出現資料不一致的問題嗎?

還是會存在的,我們可以分成3個場景來描述這個問題。

  • 查詢不一致
    • 因為Zookeeper是過半成功即代表成功,假設我們有5個節點,如果123節點寫入成功,如果這時候請求訪問到4或者5節點,那麼有可能讀取不到資料,因為可能資料還沒有同步到4、5節點中,也可以認為這算是資料不一致的問題。
    • 解決方案可以在讀取前使用sync命令。
  • leader未傳送proposal當機
    • 這也就是資料同步說過的問題。leader剛生成一個proposal,還沒有來得及傳送出去,此時leader當機,重新選舉之後作為follower,但是新的leader沒有這個proposal。

    • 這種場景下的日誌將會被丟棄。

  • leader傳送proposal成功,傳送commit前當機
    • 如果傳送proposal成功了,但是在將要傳送commit命令前當機了,如果重新進行選舉,還是會選擇zxid最大的節點作為leader,因此,這個日誌並不會被丟棄,會在選舉出leader之後重新同步到其他節點當中。
四.ZK其他小問題

zookeeper 是如何保證事務的順序一致性的?

  • 使用zxid來保證順序性。

叢集最少要幾臺機器,叢集規則是怎樣的?叢集中有 3 臺伺服器,其中一個節點當機,這個時候 Zookeeper 還可以使用嗎?

  • 叢集規則為 2N+1 (奇數)臺,N>0,即 3 臺。可以繼續使用,單數伺服器只要沒超過一半的伺服器當機就可以繼續使用。

說幾個 zookeeper 常用的命令:

  • ls path:檢視當前 znode 的子節點
  • get path:獲取節點的值
  • set:設定節點的值
  • create,delete:建立/刪除節點

會話Session:

  • 會話自然就是指Zookeeper客戶端和服務端之間的通訊,他們使用TCP長連線的方式保持通訊,通常,肯定會有心跳檢測的機制,同時他可以接受來自伺服器的Watch事件通知。

 

寄語:平靜的湖面醞釀不出精悍的水手,安逸的環境創造不出時代的偉人

相關文章