想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

Zookeeper是分佈式一致性問題的工業解決方案,是Apache Hadoop下解決分佈式一致性的一個組件,後被分離出來成爲Apache的頂級項目。

工程來源:時雅虎公司內部項目,據說雅虎內部很多項目都是以動物命名,這個動物管理員的名字起的很是形象。

被開源出來後得到開源社區的快速推進,服務端Java語言實現,棒,git有3000+的star:

https://github.com/apache/zookeeper

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

zookeeper集羣結構

集羣的角色,比較典型的是Master/Slave(主備模式),zk中的概念跟這個不一樣,包含Leader、Follower、Observer三個角色,leader提供讀和寫的能力,follower只對外提供讀的能力。

會話(session)

客戶端跟服務端交互,是先與服務端建立一個TCP長連接,會話開始,通過心跳檢測與服務端保持會話有效,向服務端發送請求和接收響應。

zk將所有的數據都加載在內存一份,同時有事務日誌文件(持久化文件),服務端會定時dump快照數據,重啓機器的時候會根據快照和事務日誌恢復內存數據庫的數據,這跟redis的AOF和RDB概念類似。

zookeeper上的數據結構

zk上的數據的結構跟linux文件系統很像,是個樹狀結構

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

節點(node)上的信息字段

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

節點類型包括:

  • 持久性節點
  • 順序持久性節點
  • 臨時節點
  • 順序臨時節點

其中臨時節點特性就是創建它的主體消失後,它就跟着消失了。後續的應用就是利用的節點的特性實現的。

事件監聽器(watcher)

這個應該是zookeeper最重要的概念之一了,zk允許用戶在特定的節點(znode)上註冊watcher,並且在特定事件觸發的時候,zk服務端會將事件通知到感興趣的客戶端上。

僞集羣的搭建:

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

zoo.cfg 配置文件

  • 拷貝三份文件
  • 修改配置zoo.cfg,區別出日誌目錄和端口號,dataDir文件下添加表示機器號的文件myid
  • 集羣配置列表,第一個端口是機器間業務通訊的端口,第二個端口是用來進行leader選舉的端口
  • 分別啓動三臺服務器

啓動成功後,命令行連接zk,可以用指令做些增刪改查的操作

telnet 127.0.0.1 2181

stat:可以看集羣的狀態信息

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

stat信息

每次事務操作,會在dataDir的目錄下的事務日誌,是序列化的二進制文件,zookeeper提供了查看事務日誌的工具類LogFormatter

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

LogFormatter轉換後的事務日誌文件

Java客戶端使用

  1. zookeeper自帶的
  2. 開源的客戶端ZkClient,實現session超時重連,watcher反覆註冊的功能,簡化開發人員的使用
  3. 開源客戶端Crurator,解決底層的細節開發工作,目前是apache的頂級項目,是使用最廣泛的zk客戶端。
  4. 提供了可讀性更新的api接口
  5. 提供了各種應用場景的抽象封裝(共享鎖服務、Master選舉機制,分佈式計數器等)
  6. guava is to java what cruator is to zookeeper
  7.  

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

cruator客戶端例子

zookeeper應用場景

利用zookeeper的特性,可以比較方便的構建分佈式應用會涉及的核心功能,比如:配置中心、命名服務、分佈式協調/通知、集羣的管理、master選舉、分佈式鎖等

以下應用基本基於zookeeper的兩大特性實現

  • 客戶端如果對zookeeper的一個數據節點註冊watcher監聽,那麼當該數據節點的內容或其子節點的列表發生變更是,zookeeper服務器就會向訂閱的客戶端發送變更通知
  • 對在zookeeper上創建的臨時節點(sequent類型),一旦客戶端與服務器之間的回話失效,那麼臨時節點會被自動清除

--配置中心:

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

配置中心

zookeeper利用推拉結合的方式,客戶端向服務端註冊自己需要關注的節點,一旦該數據發生變更,那麼服務器就會向相應的客戶端發送watcher時間通知。

客戶收到這個消息通知之後,再主動到服務端獲取最新的數據。即回調的event中包含具體的數據。

這個應用的的業務員特點:

  1. 數據不經常變化
  2. 數據量通常比較小,保存的內存裏,訪問很快
  3. 配置動態變化,不需要重啓機器,數據變化,會通知相應的客戶端
  4. 集羣共享配置一致,比如數據庫連接的配置,業務的開關,甚至一些數據量小,不經常變的業務數據如彈窗文案,活動文案也放裏邊,用於快速迭代實現功能。

--命名服務

利用zookeeper的順序節點,樹形結構的數據特點,實現命名服務:

  • 比如RPC框架中,每個服務在zookeeper中對應一個節點(serviceName),節點下存放這個服務所用到的資源,比如部署的ip列表,接口列表

這樣RPC的客戶端只需要傳對應的服務名字,和接口,就能找到對應的服務。

  • 全局唯一id的生成,UUID(通用的唯一標識碼)可以實現,但是它的缺點是:太長,包含32位字符和4個短線字符串;看不出業務含義,不方便排查問題。

使用zookeeper實現:不同的業務下創建一個節點,具體的節點下用zk的順序節點(sequent)生成id當做這個業務的全局唯一id使用

--分佈式通知/協調

ZooKeeper中特有watcher註冊與異步通知機制,能夠很好的實現分佈式環境下不同系統之間的通知與協調,實現對數據變更的實時處理。

使用方法通常是不同系統都對ZK上同一個znode進行註冊,監聽znode的變化(包括znode本身內容及子節點的),其中一個系統update了znode,那麼另一個系統能夠收到通知,並作出相應處理。

應用:

心跳檢測機制:傳統的方式是ping,複雜的話是建立長連接檢測系統和被檢測系統之間並不直接關聯起來,而是通過zookeeper上某個節點關聯,大大減少系統耦合

系統調度模式:某系統由控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工作。

管理人員在控制檯做的一些操作,實際上是修改了ZK上某些節點的狀態,而ZK就把這些變化通知給他們註冊Watcher的客戶端,即推送系統。於是,作出相應的推送任務

作彙報模式:一些類似於任務分發系統,子任務啓動後,到ZK來註冊一個臨時節點,並且定時將自己的進度進行彙報(將進度寫回這個臨時節點)

總之,使用zookeeper來進行分佈式通知和協調能夠大大降低系統之間的耦合。

--分佈式鎖

這個應用主要得益於ZooKeeper爲我們保證了數據的強一致性

即用戶只要完全相信每時每刻,zk集羣中任意節點(一個zk server)上的相同znode的數據是一定是相同的。

一個節點要麼創建成功,要麼失敗,並且只由一個客戶端創建。

獨佔鎖:

保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。

通常的做法是把ZK上的一個znode看作是一把鎖,通過create znode的方式來實現。所有客戶端都去創建/distribute_lock節點,最終成功創建的那麼客戶端也即擁有了這把鎖。

共享時序控制鎖:

Zookeeper很容易實現這個功能,實現方式是需要獲得鎖的Server,創建一個EPHEMERAL_SEQUENTIAL目錄節點。

然後調用getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點。

如果正是自己創建的,那麼它就獲得了這個鎖,如果不是,那麼它就調用exists(String path, boolean watch)方法,並監控Zookeeper上目錄節點列表的變化,一直到自己創建的節點是列表中最小編號的目錄節點,從而獲得鎖。

釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。

--master選舉

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

master選舉應用圖

有個容易理解的方案,依靠關係型數據庫主鍵的特性,集羣的機器同時往一張表裏插入數據,數據庫會自動進行主鍵衝突檢查,可以選擇插入成功的客戶端作爲master

這種方式存在一個問題就是,master機器掛了,沒有人通知

zk實現可以方便做到這一點:zk的創建節點api接口,具有強一致性,能夠保證客戶端併發創建的時候,最終一定只有一個客戶端創建成功。

  • 客戶端集羣每天定時往/master_election/(當天日期)2017-03-24/binding創 建臨時節點,只有一個成功,爲master
  • 創建失敗的在/master_election/(當天日期)2017-03-24 註冊監聽事件,當master掛了,其餘機器收到通知,重新進行選舉

--集羣管理

應用舉例:集羣機器存活性監控系統,例如:

監控系統在/clusterServers節點註冊一個watcher監聽,那麼但凡進行動態添加機器的操作,就在/clusterServers下創建一個臨時節點, /clusterServers/ip。

這樣監控系統就能夠實時的檢測到機器的變動,通過getChild方法獲取所有的臨時節點,來判斷增加的機器。

當有機器down調或者手動下線,相應臨時節點會消失,監控系統也會接收到,來處理監控服務的具體業務

具體服務器部署agent實現

zookeeper的HA設計實現

以上說了那麼多犀利實用的應用場景,它們依賴zookeeper,說明這些應用服務的高可用性依賴的zookeeper本身的HA。

zk的選舉算法

算法協議zab協議,“少數服從多數”協議一種

3.4.0版本之後Zookeeper只保留了TCP版本的FastLeaderElection選舉算法

分析選舉算法前,先熟悉瞭解下zk的一些術語定義解釋:

  • SID:服務器ID,在集羣的配置文件裏配置
  • ZXID:是一個事務ID,用來唯一標示一次服務器狀態變更,集羣中每臺機器上的ZXID可能不一樣
  • Vote:投票選舉,當集羣中機器發現自己無法檢測到leader的時候,開始嘗試進行投票
  • Quorum:過半機器數, quorum=(集羣機器數)n/2+1,比如集羣數量是3, quorum=2

集羣數量是4,quorum=2,集羣數量是5,quorum=3

當哪些情況發生時會觸發leader重新選舉呢?

當zk的一臺服務器出現以下兩種情況的時候,會進入leader選舉流程

  1. 加機器,服務器初始化
  2. 服務器運行期間無法和leader通信,leader所在服務器down掉了

對於第一種情況,即已經存在一臺leader服務器,當該機器試圖去選舉leader的時候,會被告知當前服務器的leader信息,對於該機器僅僅需要和leader建立連接,並進行狀態同步即可

主要看下第二種情況:

有兩種情況導致集羣不存在leader,一個是集羣剛啓動初始化的時候,另一種情況是運行期間leader所在服務器掛了。

無論哪種情況集羣所有集羣都處在一個找leader的狀態,稱作Looking狀態,開始向其他機器發送消息投票

開始leader選舉投票的協議規則是怎樣呢?

  • 投票的消息包含(SID,ZXID)
  • 一開始試圖投自己,把投票消息廣播出去
  • 先比較ZXID,選擇ZXID大的
  • ZXID相等的,比較SID,選擇SID大的
  • 如果自己的值大於別的服務器廣播來的消息,投票不做變更
  • 反正,更換投票,開始第二輪投票,廣播出去投票信息
  • 每輪結束統計投票,如果一臺服務器收到超過半數的相同投票,那個這個服務器對用的SID機器爲Leader

想精通分佈式以及高併發架構?那你得先搞定ZooKeeper架構原理

 

5臺機器宕機兩臺後,leader選舉的過程圖示

因此,一個錯誤的認識,爲了使zookeeper集羣能順利的選出leader,必須將zookeeper集羣的服務器數部署爲奇數。

從上邊例子能看出來部署任意臺機器都能夠正常選舉運行。部署奇數臺是官方給的建議,因爲奇數和奇數+1的容災能力是一樣的。比如:

5臺服務器,能夠對2臺機器掛掉的情況進行容災

6臺服務器,能夠對2臺機器掛掉的情況進行容災,如果掛掉3臺,剩下的機器就無法實現過半了。

推薦閱讀:

馬士兵教育2020最新最全版馬士兵高併發多線程450分鐘教程,【從入門到精通】

面試美團被JVM慘虐?阿里P9架構師用500分鐘把JVM從入門講到實戰#合集

硬核!清華雙大佬馬士兵VS周志磊把TCP協議、高併發負載均衡、多線程、JVM底層給講清楚了

四十歲的Java程序員活該被淘汰?馬士兵給所有Java程序員的忠告,告知當代應屆生進大廠的祕訣

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章