天天看點

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記

文章目錄

  • 《從Paxos到zookeeper分布式一緻性原理與實踐》筆記
  • 一、概念
  • 二、一緻性協調
    • 2.1 2PC (Two-Phase Commit)
      • 前提
      • 基本算法
      • 算法示意
      • 缺點
    • 2.2 3PC (Three-Phase Commit)
      • 基本算法
        • 1.CanCommit階段
        • 2.PreCommit階段
        • 3.DoCommit階段
      • 算法示意
    • 2PC和3PC差別
      • 優缺點
    • 2.3 Paxos(解決單點問題)
      • Paxos算法原理
      • Paxos算法過程
  • 三、Zookeeper
    • 3.1、初識Zookeeper
      • 3.1.1 zookeeper可以保證如下分布式一緻性特性
      • 3.1.2 zookeeper的四個設計目标
      • 3.1.3 zookeeper的基本概念
        • 1、叢集角色
        • 2、會話
        • 3、資料節點
        • 4、版本
        • 5、Watcher
        • 6、ACL
    • 3.2、ZAB
      • 3.2.1、ZAB協定
      • 3.2.2、ZAB兩種基本的模式:崩潰恢複和消息廣播。
        • 崩潰恢複
        • 消息廣播
        • 基本特性
        • 資料同步
      • 3.2.3、ZAB協定原理
    • 3.3、ZAB與Paxos的聯系和差別
  • 四、使用Zookeeper
    • 4.1、部署與運作
    • 4.2、用戶端腳本
    • 4.3、Java用戶端API使用
    • 4.4、開源用戶端
      • 4.4.1、ZkClient
        • 4.1.1 添加依賴
      • 4.4.2、Curator用戶端
        • 4.4.2.1 添加依賴
        • 4.4.2.2 建立會話
        • 4.4.2.3 建立節點
        • 4.4.2.7 異步接口
        • 4.2.8 節點監聽
        • 4.2.9 子節點監聽
        • 4.2.10 Master選舉
        • 4.2.11 分布式鎖
        • 4.4.2.12 分布式計數器 DistributedAtomicInteger
        • 4.4.2.13 分布式Barrier
      • 4.4.3、Curator工具類
        • 4.4.3.1 ZKPaths
        • 4.4.3.2 EnsurePath
  • 五、Zookeeper應用場景
    • 5.1、典型應用場景及實作
      • 5.1.1 資料釋出/訂閱
      • 5.1.2 負載均衡
      • 5.1.3 命名服務
      • 5.1.4 分布式協調/通知
      • 5.1.5 叢集管理
      • 5.1.6 Master選舉
      • 5.1.7 分布式鎖
      • 5.1.8 分布式隊列
    • 5.2、zk在大型分布式系統中的應用
      • 5.2.1 Hadoop
        • 5.2.1.1 YARN
        • 5.2.1.2 ResourceManager HA
        • 5.2.1.3 主備切換
        • 5.2.1.4 隔離(Fencing)
        • 5.2.1.5 ResourceManager狀态存儲
      • 5.2.2 HBase
      • 5.2.3 Kafka
        • 5.2.3.1 Broker注冊
        • 5.2.3.2 Topic注冊
        • 5.2.3.3 生産者負載均衡
        • 5.2.3.4 消費者負載均衡
        • 5.2.3.5 消費分區與消費者的關系
        • 5.2.3.6 消息消費進度Offset記錄
        • 5.2.3.7 消費者注冊
        • 5.2.3.8 負載均衡
  • 《從Paxos到zookeeper分布式一緻性原理與實踐》筆記
  • 第6章ZooKeeper技術内幕
    • 6.1 系統模型
      • 6.1.1 資料模型
      • 6.1.2 節點特性
      • 6.1.3 版本——保證分布式資料原子性操作
      • 6.1.4 Watcher——資料變更的通知
      • 6.1.5 ACL——保障資料的安全
    • 6.2 序列化與協定
      • 6.2.1 Jute介紹
      • 6.2.2 使用Jute進行序列化
      • 6.2.3 深入Jute
      • 6.2.4 通信協定
      • 6.2.4.1 請求協定
      • 6.2.4.2 響應協定
      • 6.2.5 stat狀态說明
    • 6.3 用戶端
      • 6.3.1 一次會話的建立過程
      • 6.3.2 伺服器位址清單
      • 6.3.3 ClientCnxn:網絡I/O
    • 6.4 會話
      • 6.4.1 會話狀态
      • 6.4.2 會話建立
      • 6.4.3 會話管理
      • 6.4.4 會話清理
      • 6.4.5 重連
        • 6.4.5.1 重連狀态(CONNECTED & EXPIRED)
        • 6.4.5.2 重連異常: CONNECTION_LOSS(連接配接斷開)和SESSION_EXPIRED(會話過期)
    • 6.5 伺服器啟動
      • 6.5.1 單機版伺服器啟動
        • 6.5.1.1 單機版伺服器啟動 - 預啟動
        • 6.5.1.2 單機版伺服器啟動 - 初始化
      • 6.5.2 叢集版伺服器啟動
        • 6.5.2.1 叢集版伺服器啟動 - 預啟動
        • 6.5.2.2 叢集版伺服器啟動 - 初始化
    • 6.6 Leader選舉
      • 6.6.1 Leader選舉概述
        • 6.6.1.1 伺服器啟動時期的Leader選舉
        • 6.6.1.2 伺服器運作時期的Leader選舉
      • 6.6.2 Leader選舉的算法分析
        • 6.6.2.1 術語解釋
        • 6.6.2.2 進入leader選舉
      • 6.6.3 Leader選舉的實作細節
    • 6.7 各伺服器角色介紹
      • 6.7.1 Leader
        • 6.7.1.1 請求處理鍊
        • 6.7.1.2 LearnerHandler
      • 6.7.2 Follower
      • 6.7.3 Observer
      • 6.7.4 叢集間消息通信
    • 6.8 請求處理
      • 6.8.1 會話建立請求
        • 6.8.1.1 請求接收
        • 6.8.1.2 會話建立
        • 6.8.1.3 預處理
        • 6.8.1.4 事務處理
        • 6.8.1.5 事務應用
        • 6.8.1.6 會話響應
      • 6.8.2 SetData請求
      • 6.8.3 事務請求轉發
      • 6.8.4 GetData請求
      • 6.9 資料與存儲
      • 6.9.1 記憶體資料
      • 6.9.2 事務日志
        • 6.9.2.1 檔案存儲
        • 6.9.2.2 日志格式
        • 6.9.2.3 日志寫入
        • 6.9.2.4 日志截斷
      • 6.9.3 snapshot——資料快照
        • 6.9.3.1 檔案存儲
        • 6.9.3.2 資料快照
      • 6.9.4 初始化
        • 6.9.4.1 初始化流程
      • 6.9.5 資料同步
  • 七、Zookeeper運維
    • 7.1 配置參數
    • 7.1 2四字指令
  • reference

================

一、概念

ACID: Automaticy、consistency、isolation、 Durability

CAP: consistency、 Availability、 Partition tolerance

BASE: Basically Available、 Soft state、 Eventually consistent

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

二、一緻性協調

協調者

在分布式系統中,每一個機器節點雖然都能明确的知道自己執行的事務是成功還是失敗,但是卻無法知道其他分布式節點的事務執行情況。是以,當一個事務要跨越多個分布式節點的時候,為了保證該事務可以滿足ACID,就要引入一個協調者(Cooradinator)。其他的節點被稱為參與者(Participant)。協調者負責排程參與者的行為,并最終決定這些參與者是否要把事務進行送出。

2.1 2PC (Two-Phase Commit)

前提

二階段送出算法的成立基于以下假設:

  • 1/ 該分布式系統中,存在一個節點作為協調者(Coordinator),其他節點作為參與者(Cohorts)。且節點之間可以進行網絡通信。
  • 2/ 所有節點都采用預寫式日志,且日志被寫入後即被保持在可靠的儲存設備上,即使節點損壞不會導緻日志資料的消失。
  • 3/ 所有節點不會永久性損壞,即使損壞後仍然可以恢複。

基本算法

二階段送出協定主要分為來個階段:準備階段和送出階段。

** 第一階段(送出請求階段) **

  • 1.協調者節點向所有參與者節點詢問是否可以執行送出操作,并開始等待各參與者節點的響應。
  • 2.參與者節點執行詢問發起為止的所有事務操作,并将Undo資訊和Redo資訊寫入日志。
  • 3.各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它傳回一個"同意"消息;如果參與者節點的事務操作實際執行失敗,則它傳回一個"中止"消息。

有時候,第一階段也被稱作投票階段,即各參與者投票是否要繼續接下來的送出操作。

第二階段(送出執行階段)

成功

當協調者節點從所有參與者節點獲得的相應消息都為"同意"時:

  • 1.協調者節點向所有參與者節點發出"正式送出"的請求。
  • 2.參與者節點正式完成操作,并釋放在整個事務期間内占用的資源。
  • 3.參與者節點向協調者節點發送"完成"消息。
  • 4.協調者節點收到所有參與者節點回報的"完成"消息後,完成事務。

失敗

如果任一參與者節點在第一階段傳回的響應消息為"終止",或者 協調者節點在第一階段的詢問逾時之前無法擷取所有參與者節點的響應消息時:

  • 1.協調者節點向所有參與者節點發出"復原操作"的請求。
  • 2.參與者節點利用之前寫入的Undo資訊執行復原,并釋放在整個事務期間内占用的資源。
  • 3.參與者節點向協調者節點發送"復原完成"消息。
  • 4.協調者節點收到所有參與者節點回報的"復原完成"消息後,取消事務。

有時候,第二階段也被稱作完成階段,因為無論結果怎樣,協調者都必須在此階段結束目前事務。

算法示意

協調者                                              參與者
                            QUERY TO COMMIT
              -------------------------------->
                            VOTE YES/NO           prepare*/abort*
              <-------------------------------
commit*/abort*                COMMIT/ROLLBACK
              -------------------------------->
                            ACKNOWLEDGMENT        commit*/abort*
              <--------------------------------  
end
           
  • 所标記的操作意味着此類操作必須記錄在穩固存儲上.

缺點

1、同步阻塞問題。執行過程中,所有參與節點都是事務阻塞型的。當參與者占有公共資源時,其他第三方節點通路公共資源不得不處于阻塞狀态。

2、單點故障。由于協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那麼所有的參與者還都處于鎖定事務資源的狀态中,而無法繼續完成事務操作。(如果是協調者挂掉,可以重新選舉一個協調者,但是無法解決因為協調者當機導緻的參與者處于阻塞狀态的問題)

3、資料不一緻。在二階段送出的階段二中,當協調者向參與者發送commit請求之後,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導緻隻有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之後就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務送出。于是整個分布式系統便出現了資料部一緻性的現象。

4、二階段無法解決的問題:協調者再發出commit消息之後當機,而唯一接收到這條消息的參與者同時也當機了。那麼即使協調者通過選舉協定産生了新的協調者,這條事務的狀态也是不确定的,沒人知道事務是否被已經送出。

2.2 3PC (Three-Phase Commit)

除了引入逾時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段送出就有CanCommit、PreCommit、DoCommit三個階段。

基本算法

1.CanCommit階段

3PC的CanCommit階段其實和2PC的準備階段很像。

協調者向參與者發送commit請求,參與者如果可以送出就傳回Yes響應,否則傳回No響應。

2.PreCommit階段

協調者(Coordinator)根據參與者(Cohort)的反應情況來決定是否可以繼續事務的PreCommit操作。

根據響應情況,有以下兩種可能。

執行送出

假如Coordinator從所有的Cohort獲得的回報都是Yes響應,那麼就會進行事務的預執行:

發送預送出請求。Coordinator向Cohort發送PreCommit請求,并進入Prepared階段。

事務預送出。Cohort接收到PreCommit請求後,會執行事務操作,并将undo和redo資訊記錄到事務日志中。

響應回報。如果Cohort成功的執行了事務操作,則傳回ACK響應,同時開始等待最終指令。

中斷事務

假如有任何一個Cohort向Coordinator發送了No響應,或者等待逾時之後,Coordinator都沒有接到Cohort的響應,那麼就中斷事務:

發送中斷請求。Coordinator向所有Cohort發送abort請求。

中斷事務。Cohort收到來自Coordinator的abort請求之後(或逾時之後,仍未收到Cohort的請求),執行事務的中斷。

3.DoCommit階段

該階段進行真正的事務送出,也可以分為以下兩種情況:

** 執行送出 **

A.發送送出請求。Coordinator接收到Cohort發送的ACK響應,那麼他将從預送出狀态進入到送出狀态。并向所有Cohort發送doCommit請求。

B.事務送出。Cohort接收到doCommit請求之後,執行正式的事務送出。并在完成事務送出之後釋放所有事務資源。

C.響應回報。事務送出完之後,向Coordinator發送ACK響應。

D.完成事務。Coordinator接收到所有Cohort的ACK響應之後,完成事務。

** 中斷事務 **

Coordinator沒有接收到Cohort發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應逾時),那麼就會執行中斷事務。

在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者rebort請求時,會在等待逾時之後,會繼續進行事務的送出。(其實這個應該是基于機率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那麼協調者産生PreCommit請求的前提條件是他在第二階段開始之前,收到所有參與者的CanCommit響應都是Yes。(一旦參與者收到了PreCommit,意味他知道大家其實都同意修改了)是以,一句話概括就是,當進入第三階段時,由于網絡逾時等原因,雖然參與者沒有收到commit或者abort響應,但是他有理由相信:成功送出的幾率很大。 )

算法示意

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

2PC和3PC差別

差別 2PC 3PC
階段 送出事務請求 以及 執行事務送出 隻有協調者有逾時判斷。3PC将2PC的送出事務請求分成了CanCommit以及PreCommit
逾時 隻有協調者有逾時判斷 3PC上參與者和協調者都有逾時的判斷

優缺點

優點:降低參與者阻塞範圍,并能夠在出現單點故障後繼續達成一緻

缺點:引入preCommit階段,在這個階段如果出現網絡分區,協調者無法與參與者正常通信,參與者依然會進行事務送出,造成資料不一緻。

無論是二階段送出還是三階段送出都無法徹底解決分布式的一緻性問題。Google Chubby的作者Mike Burrows說過, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos. 意即世上隻有一種一緻性算法,那就是Paxos,所有其他一緻性算法都是Paxos算法的不完整版。

2.3 Paxos(解決單點問題)

首先推薦larmport自己寫的和paxos相關的三篇論文:<< The Part-Time Parliament>>、<>、<>

Paxos算法原理

Paxos算法是Lesile Lamport提出的一種基于消息傳遞且具有高度容錯特性的一緻性算法。分布式系統中的節點通信存在兩種模型: 共享記憶體和消息傳遞。基于消息傳遞通信模型的分布式系統,不可避免會發生程序變慢被殺死,消息延遲、丢失、重複等問題,Paxos算法就是在存在以上異常的情況下仍能保持一緻性的協定。

Paxos算法使用一個希臘故事來描述,在Paxos中,存在三種角色,分别為Propose(提議者,用來發出提案proposal), Acceptor(接受者,可以接受或拒絕提案), Learner(學習者,學習被標明的提案,當提案被超過半數的Acceptor接受後為被準許)。下面更精确的定義Paxos要解決的問題:

  • 1.決議(value)隻有在被proposer提出後才能被準許
  • 2.在一次Paxos算法的執行執行個體中,隻準許(chose)一個value
  • 3.learner隻能獲得被準許(chosen)的value。

一般的Paxos說明都會采用不斷加強條件的方式來最終達成一緻性條件,這樣的方式看上去不太容易了解,容易讓人以為是一步一步推出來的,實際上更像一緻性的一種充分不必要條件。

Paxos算法過程

首先有一個遞增的編号生成器,可以保證生成的需要遞增,用來為Proposer生成提議的編号。

第一階段 prepare階段:

  • 1.Proposer生成編号n并使用v作為value的提案(n, v)發送prepare請求給Acceptor中的大多數。
  • 2.Acceptor收到prepare請求後,如果提案的編号大于它已經回複的所有prepare消息,則Acceptor将上次接收的提案和已接收的value回複給Proposer,并承若不再回複編号小于n的提案。如果Acceptor沒有接收過prepare請求或收到的prepare請求的編号都比n小則回複該prepare請求。

第二階段 準許階段:

  • 1.當一個Proposer收到了多數Acceptor對prepare的回複後,就進入準許階段。它要想回複prepare請求的Acceptor發送accept請求,包括編号n和上一階段中傳回的消息中的value。
  • 2.在不違背自己向其他Proposer的承諾的前提下,Acceptor收到accept請求後接收這個請求。

http://codemacro.com/2014/10/15/explain-poxos/

https://www.zhihu.com/question/19787937

https://baike.baidu.com/item/Paxos 算法

基于消息傳遞且具有高度容錯性的一緻性算法。Paxos算法要解決的問題就是如何在可能發生幾起當機或網絡異常的分布式系統中,快速且正确地在叢集内部對某個資料的值達成一緻,并且保證不論發生以上任何異常,都不會破壞整個系統的一緻性。

三、Zookeeper

3.1、初識Zookeeper

zookeeper是一個典型的分布式資料一緻性的解決方案,分布式應用程式可以基于它實作資料釋出/訂閱、負載均衡、命名服務、分布式協調/通知、叢集管理、master選舉、分布式鎖和分布式隊列等。

3.1.1 zookeeper可以保證如下分布式一緻性特性

  • 1、順序一緻性:從同一個用戶端發起的事務請求,最終将會嚴格地按照其發起順序被應用到Zookeeper中;
  • 2、原子性:所有事務的請求結果在整個叢集中所有機器上的應用情況是一緻的,也就是說,要麼在整個叢集中所有機器上都成功應用了某一個事務,要麼都沒有應用,沒有中間狀态;
  • 3、單一視圖:無論用戶端連接配接的是哪個Zookeeper伺服器,其看到的服務端資料模型都是一緻的。
  • 4.可靠性:一旦服務端成功應用了一個事務,并完成對用戶端的響應,那麼該事務所引起的服務端狀态變更将會被一直保留下來,除非有另一個事務又對其進行了變更。
  • 5、實時性:Zookeeper僅僅保證在一定的時間内,用戶端最終一定能夠從服務端上讀到最終的資料狀态。

3.1.2 zookeeper的四個設計目标

1、簡單的資料模型:能夠通過一個共享的、樹型結構的名字空間來進行互相協調。

2、可以建構叢集:Zookeeper使得分布式程式能夠通過一個共享的樹形結構的名字空間來進行互相協調,即Zookeeper伺服器記憶體中的資料模型由一系列被稱為ZNode的資料節點組成,Zookeeper将全量的資料存儲在記憶體中,以此來提高伺服器吞吐、減少延遲的目的。

3、順序通路:對于來自用戶端的每個更新請求,Zookeeper都會配置設定一個全局唯一的遞增編号,這個編号反映了所有事務操作的先後順序。

4、高性能:Zookeeper将全量資料存儲在記憶體中,并直接服務于用戶端的所有非事務請求,是以它尤其适用于以讀操作為主的應用場景。

3.1.3 zookeeper的基本概念

1、叢集角色

最典型的叢集就是Master/Slave模式(主備模式),此情況下把所有能夠處理寫操作的機器稱為Master機器,把所有通過異步複制方式擷取最新資料,并提供讀服務的機器為Slave機器。Zookeeper引入了Leader、Follower、Observer三種角色,Zookeeper叢集中的所有機器通過Leaser選舉過程來標明一台被稱為Leader的機器,Leader伺服器為用戶端提供寫服務,Follower和Observer提供讀服務,但是Observer不參與Leader選舉過程,不參與寫操作的"過半寫成功"政策,Observer可以在不影響寫性能的情況下提升叢集的性能。

leader:

是整個叢集工作機制中的核心,其主要工作有:

1、事務請求的唯一排程和處理者,保證叢集事務處理的順序性。

2、叢集内部各伺服器的排程者。

follower:

是zookeeper叢集狀态的跟随者,其主要工作是:

1、處理用戶端的非事務請求,轉發事務請求給leader伺服器。

2、參與事務請求proposal的投票

3、參與leader選舉投票

observer

和follower唯一的差別在于,observer伺服器隻提供非事務服務,不參與任何形式的投票,包括事務請求proposal的投票和leader選舉投票。

通常在不影響叢集事務處理能力的前提下提升叢集的非事務處理能力。

2、會話

指用戶端會話,一個用戶端連接配接是指用戶端和服務端之間的一個TCP長連接配接,Zookeeper對外的服務端口預設為2181,用戶端啟動的時候,首先會與伺服器建立一個TCP連接配接,從第一次連接配接建立開始,用戶端會話的生命周期也開始了,通過這個連接配接,用戶端能夠心跳檢測與伺服器保持有效的會話,也能夠向Zookeeper伺服器發送請求并接受響應,同時還能夠通過該連接配接接受來自伺服器的Watch事件通知。

3、資料節點

第一類指構成叢集的機器,稱為機器節點,第二類是指資料模型中的資料單元,稱為資料節點-Znode,Zookeeper将所有資料存儲在記憶體中,資料模型是一棵樹,由斜杠/進行分割的路徑,就是一個ZNode,如/foo/path1,每個ZNode都會儲存自己的資料記憶體,同時還會儲存一些列屬性資訊。ZNode分為持久節點和臨時節點兩類,持久節點是指一旦這個ZNode被建立了,除非主動進行ZNode的移除操作,否則這個ZNode将一直儲存在Zookeeper上,而臨時節點的生命周期和用戶端會話綁定,一旦用戶端會話失效,那麼這個用戶端建立的所有臨時節點都會被移除。另外,Zookeeper還允許使用者為每個節點添加一個特殊的屬性:SEQUENTIAL。一旦節點被标記上這個屬性,那麼在這個節點被建立的時候,Zookeeper會自動在其節點後面追加一個整形數字,其是由父節點維護的自增數字。

4、版本

對于每個ZNode,Zookeeper都會為其維護一個叫作Stat的資料結構,Stat記錄了這個ZNode的三個資料版本,分别是version(目前ZNode的版本)、cversion(目前ZNode子節點的版本)、aversion(目前ZNode的ACL版本)。

5、Watcher

Zookeeper允許使用者在指定節點上注冊一些Watcher,并且在一些特定事件觸發的時候,Zookeeper服務端會将事件通知到感興趣的用戶端。

6、ACL

Zookeeper采用ACL(Access Control Lists)政策來進行權限控制,其定義了如下五種權限:

  • CREATE:建立子節點的權限。
  • READ:擷取節點資料和子節點清單的權限。
  • WRITE:更新節點資料的權限。
  • DELETE:删除子節點的權限。
  • ADMIN:設定節點ACL的權限。

3.2、ZAB

Zookeeper為分布式應用提供高效且可靠的分布式協調服務,提供了統一命名服務、配置管理、分布式鎖等分布式的基礎服務。Zookeeper并沒有直接采用Paxos算法,而是采用了一種被稱為** ZAB(Zookeeper Atomic Broadcast) ** 的一緻性協定。

Zookeeper使用了Zookeeper Atomic Broadcast(ZAB,Zookeeper原子消息廣播協定)的協定作為其資料一緻性的核心算法。ZAB協定是為Zookeeper專門設計的一種__支援崩潰恢複的原子廣播協定__。

3.2.1、ZAB協定

所有事務請求必須由一個全局唯一的伺服器來協調處理,這樣的伺服器被稱為leader伺服器,而餘下的其他伺服器則成為follower伺服器。leader伺服器負責将一個用戶端事務請求轉換成一個事務proposal,并将該proposal分發給叢集中所有的follower伺服器。之後leader伺服器需要等待所有follower伺服器的回報,一旦超過半數的follower伺服器進行了正确的回報後,那麼leader就會再次向所有的follower伺服器分發commit消息,要求其将前一個proposal進行送出。

ZAB協定需要確定那些已經在leader伺服器上送出的事務最終被所有伺服器都送出。ZAB協定需要確定丢棄那些隻在leader伺服器上被提出的事務。如果讓leader選舉算法能夠保證新選舉出來的leader伺服器擁有叢集中所有機器最高編号(ZXID)的事務proposal,那麼就可以保證這個新選舉出來的leader一定具有所有已經送出的提案。

3.2.2、ZAB兩種基本的模式:崩潰恢複和消息廣播。

崩潰恢複

當整個服務架構啟動過程中或Leader伺服器出現網絡中斷、崩潰退出與重新開機等異常情況時,ZAB協定就會進入恢複模式并選舉産生新的Leader伺服器。

當選舉産生了新的Leader伺服器,同時叢集中已經有過半的機器與該Leader伺服器完成了狀态同步之後,ZAB協定就會退出恢複模式,那麼整個服務架構就可以進入消息廣播模式。

Leader選舉算法不僅僅需要讓Leader自身知道已經被選舉為Leader,同時還需要讓叢集中的所有其他機器也能夠快速地感覺到選舉産生的新的Leader伺服器。

當Leader伺服器出現崩潰或者機器重新開機、叢集中已經不存在過半的伺服器與Leader伺服器保持正常通信時,那麼在重新開始新的一輪的原子廣播事務操作之前,所有程序首先會使用崩潰恢複協定來使彼此到達一緻狀态,于是整個ZAB流程就會從消息廣播模式進入到崩潰恢複模式。

消息廣播

ZAB協定的消息廣播過程使用原子廣播協定,類似于一個二階段送出過程,針對用戶端的事務請求,Leader伺服器會為其生成對應的事務Proposal,并将其發送給叢集中其餘所有的機器,然後再分别收集各自的選票,最後進行事務送出。

整個消息廣播協定是基于具有FIFO特性的TCP協定來進行網絡通信的,是以能夠很容易保證消息廣播過程中消息接受與發送的順序性。

整個消息廣播過程中,Leader伺服器會為每個事務請求生成對應的Proposal來進行廣播,并且在廣播事務Proposal之前,Leader伺服器會首先為這個事務Proposal配置設定一個全局單調遞增的唯一ID,稱之為事務ID(ZXID),由于ZAB協定需要保證每個消息嚴格的因果關系,是以必須将每個事務Proposal按照其ZXID的先後順序來進行排序和處理。

當一台同樣遵守ZAB協定的伺服器啟動後加入到叢集中,如果此時叢集中已經存在一個Leader伺服器在負責進行消息廣播,那麼加入的伺服器就會自覺地進入資料恢複模式:找到Leader所在的伺服器,并與其進行資料同步,然後一起參與到消息廣播流程中去。

基本特性

ZAB協定規定了如果一個事務Proposal在一台機器上被處理成功,那麼應該在所有的機器上都被處理成功,哪怕機器出現故障崩潰。

ZAB協定需要確定那些已經在Leader伺服器上送出的事務最終被所有伺服器都送出。

ZAB協定需要確定丢棄那些隻在Leader伺服器上被提出的事務

如果在崩潰恢複過程中出現一個需要被丢棄的提議,那麼在崩潰恢複結束後需要跳過該事務Proposal

在崩潰恢複過程中需要處理的特殊情況,就決定了ZAB協定必須設計這樣的

Leader選舉算法

能夠確定送出已經被Leader送出的事務的Proposal,同時丢棄已經被跳過的事務Proposal。如果讓Leader選舉算法能夠保證新選舉出來的Leader伺服器擁有叢集中所有機器最高編号(ZXID最大)的事務Proposal,那麼就可以保證這個新選舉出來的Leader一定具有所有已經送出的提議,更為重要的是如果讓具有最高編号事務的Proposal機器稱為Leader,就可以省去Leader伺服器查詢Proposal的送出和丢棄工作這一步驟了。

資料同步

完成Leader選舉後,在正式開始工作前,Leader伺服器首先會确認日志中的所有Proposal是否都已經被叢集中的過半機器送出了,即是否完成了資料同步。

下面分析ZAB協定如何處理需要丢棄的事務Proposal的,ZXID是一個64位的數字,其中低32位可以看做是一個簡單的單調遞增的計數器,針對用戶端的每一個事務請求,Leader伺服器在産生一個新的事務Proposal時,都會對該計數器進行加1操作;而高32位則代表了Leader周期epoch的編号,每當選舉産生一個新的Leader時,就會從這個Leader上取出其本地日志中最大事務Proposal的ZXID,并解析出epoch['ɛpək]值,然後加1,之後以該編号作為新的epoch,低32位從0來開始生成新的ZXID,ZAB協定通過epoch号來區分Leader周期變化的政策,能夠有效地避免不同的Leader伺服器錯誤地使用不同的ZXID編号提出不一樣的事務Proposal的異常情況。當一個包含了上一個Leader周期中尚未送出過的事務Proposal的伺服器啟動時,其肯定無法成為Leader,因為目前叢集中一定包含了一個Quorum(過半)集合,該集合中的機器一定包含了更高epoch的事務的Proposal,是以這台機器的事務Proposal并非最高,也就無法成為Leader。

3.2.3、ZAB協定原理

ZAB主要包括消息廣播和崩潰恢複兩個過程,進一步可以分為三個階段,分别是發現(Discovery)、同步(Synchronization)、廣播(Broadcast)階段。ZAB的每一個分布式程序會循環執行這三個階段,稱為主程序周期。

  • 發現,選舉産生PL(prospective leader),PL收集Follower epoch(cepoch),根據Follower的回報,PL産生newepoch(每次選舉産生新Leader的同時産生新epoch)。
  • 同步,PL補齊相比Follower多數派缺失的狀态、之後各Follower再補齊相比PL缺失的狀态,PL和Follower完成狀态同步後PL變為正式Leader(established leader)。
  • 廣播,Leader處理用戶端的寫操作,并将狀态變更廣播至Follower,Follower多數派通過之後Leader發起将狀态變更落地(deliver/commit)。

在正常運作過程中,ZAB協定會一直運作于階段三來反複進行消息廣播流程,如果出現崩潰或其他原因導緻Leader缺失,那麼此時ZAB協定會再次進入發現階段,選舉新的Leader。

每個程序都有可能處于如下三種狀态之一

  • LOOKING:Leader選舉階段。
  • FOLLOWING:Follower伺服器和Leader伺服器保持同步狀态。
  • LEADING:Leader伺服器作為主程序上司狀态。

一個Follower隻能和一個Leader保持同步,Leader程序和所有與所有的Follower程序之間都通過心跳檢測機制來感覺彼此的情況。若Leader能夠在逾時時間内正常收到心跳檢測,那麼Follower就會一直與該Leader保持連接配接,而如果在指定時間内Leader無法從過半的Follower程序那裡接收到心跳檢測,或者TCP連接配接斷開,那麼Leader會放棄目前周期的上司,轉換到LOOKING狀态。

3.3、ZAB與Paxos的聯系和差別

聯系:

  • 都存在一個類似于Leader程序的角色,由其負責協調多個Follower程序的運作。
  • Leader程序都會等待超過半數的Follower做出正确的回報後,才會将一個提議進行送出。
  • 在ZAB協定中,每個Proposal中都包含了一個epoch值,用來代表目前的Leader周期,在Paxos算法中,同樣存在這樣的一個辨別,名字為Ballot。

差別:

  • Paxos算法中,新選舉産生的主程序會進行兩個階段的工作,第一階段稱為讀階段,新的主程序和其他程序通信來收集主程序提出的提議,并将它們送出。第二階段稱為寫階段,目前主程序開始提出自己的提議。
  • ZAB協定在Paxos基礎上添加了同步階段,此時,新的Leader會确儲存在過半的Follower已經送出了之前的Leader周期中的所有事務Proposal。
  • ZAB協定主要用于建構一個高可用的分布式資料主備系統,而Paxos算法則用于建構一個分布式的一緻性狀态機系統。

四、使用Zookeeper

4.1、部署與運作

4.2、用戶端腳本

4.3、Java用戶端API使用

4.4、開源用戶端

4.4.1、ZkClient

ZkClient是在Zookeeper原聲API接口之上進行了包裝,是一個更易用的Zookeeper用戶端,其内部還實作了諸如Session逾時重連、Watcher反複注冊等功能

4.1.1 添加依賴

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>
           

4.4.2、Curator用戶端

Curator解決了很多Zookeeper用戶端非常底層的細節開發工作,包括連接配接重連,反複注冊Watcher和NodeExistsException異常等,現已成為Apache的頂級項目

4.4.2.1 添加依賴

在pom.xml檔案中添加如下内容即可。

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.4.2</version>
</dependency>
           

4.4.2.2 建立會話

Curator除了使用一般方法建立會話外,還可以使用fluent風格進行建立。

package com.hust.grid.leesf.curator.examples;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class Create_Session_Sample {
    public static void main(String[] args) throws Exception {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", 5000, 3000, retryPolicy);
        client.start();
        System.out.println("Zookeeper session1 established. ");
        CuratorFramework client1 = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
                .sessionTimeoutMs(5000).retryPolicy(retryPolicy).namespace("base").build();
        client1.start();
        System.out.println("Zookeeper session2 established. ");        
    }
}
           

值得注意的是session2會話含有隔離命名空間,即用戶端對Zookeeper上資料節點的任何操作都是相對/base目錄進行的,這有利于實作不同的Zookeeper的業務之間的隔離。

4.4.2.3 建立節點

通過使用Fluent風格的接口,開發人員可以進行自由組合來完成各種類型節點的建立

package com.hust.grid.leesf.curator.examples;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

public class Create_Node_Sample {
    public static void main(String[] args) throws Exception {
        String path = "/zk-book/c1";
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
                .sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
        client.start();
        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, "init".getBytes());
        System.out.println("success create znode: " + path);
    }
}
           

4.4.2.7 異步接口

如同Zookeeper原生API提供了異步接口,Curator也提供了異步接口。在Zookeeper中,所有的異步通知事件處理都是由EventThread這個線程來處理的,EventThread線程用于串行處理所有的事件通知,其可以保證對事件處理的順序性,但是一旦碰上複雜的處理單元,會消耗過長的處理時間,進而影響其他事件的處理,Curator允許使用者傳入Executor執行個體,這樣可以将比較複雜的事件處理放到一個專門的線程池中去。

package com.hust.grid.leesf.curator.examples;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

public class Create_Node_Background_Sample {
    static String path = "/zk-book";
    static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
    static CountDownLatch semaphore = new CountDownLatch(2);
    static ExecutorService tp = Executors.newFixedThreadPool(2);

    public static void main(String[] args) throws Exception {
        client.start();
        System.out.println("Main thread: " + Thread.currentThread().getName());

        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("event[code: " + event.getResultCode() + ", type: " + event.getType() + "]" + ", Thread of processResult: " + Thread.currentThread().getName());
                System.out.println();
                semaphore.countDown();
            }
        }, tp).forPath(path, "init".getBytes());

        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("event[code: " + event.getResultCode() + ", type: " + event.getType() + "]" + ", Thread of processResult: " + Thread.currentThread().getName());
                semaphore.countDown();
            }
        }).forPath(path, "init".getBytes());

        semaphore.await();
        tp.shutdown();
    }
}
           

運作結果:

Main thread: main
event[code: -110, type: CREATE], Thread of processResult: main-EventThread
event[code: 0, type: CREATE], Thread of processResult: pool-3-thread-1
           

其中,建立節點的事件由線程池自己處理,而非預設線程處理。

Curator除了提供很便利的API,還提供了一些典型的應用場景,開發人員可以使用參考更好的了解如何使用Zookeeper用戶端,所有的都在recipes包中,隻需要在pom.xml中添加如下依賴即可

<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.4.2</version>
</dependency>
           

4.2.8 節點監聽

4.2.9 子節點監聽

4.2.10 Master選舉

4.2.11 分布式鎖

為了保證資料的一緻性,經常在程式的某個運作點需要進行同步控制。以流水号生成場景為例,普通的背景應用通常采用時間戳方式來生成流水号,但是在使用者量非常大的情況下,可能會出現并發問題。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class Recipes_Lock {
    static String lock_path = "/curator_recipes_lock_path";
    static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
            .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();

    public static void main(String[] args) throws Exception {
        client.start();
        final InterProcessMutex lock = new InterProcessMutex(client, lock_path);
        final CountDownLatch down = new CountDownLatch(1);
        for (int i = 0; i < 30; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        down.await();
                        lock.acquire();
                    } catch (Exception e) {
                    }
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
                    String orderNo = sdf.format(new Date());
                    System.out.println("生成的訂單号是 : " + orderNo);
                    try {
                        lock.release();
                    } catch (Exception e) {
                    }
                }
            }).start();
        }
        down.countDown();
    }
}
           

4.4.2.12 分布式計數器 DistributedAtomicInteger

分布式計數器的典型應用是統計系統的線上人數,借助Zookeeper也可以很友善實作分布式計數器功能:指定一個Zookeeper資料節點作為計數器,多個應用執行個體在分布式鎖的控制下,通過更新節點的内容來實作計數功能。

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;

public class Recipes_DistAtomicInt {
    static String distatomicint_path = "/curator_recipes_distatomicint_path";
    static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
            .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();

    public static void main(String[] args) throws Exception {
        client.start();
        DistributedAtomicInteger atomicInteger = new DistributedAtomicInteger(client, distatomicint_path,
                new RetryNTimes(3, 1000));
        AtomicValue<Integer> rc = atomicInteger.add(8);
        System.out.println("Result: " + rc.succeeded());
    }
}
           

4.4.2.13 分布式Barrier

如同JDK的CyclicBarrier,Curator提供了DistributedBarrier來實作分布式Barrier。

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.barriers.DistributedBarrier;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class Recipes_Barrier {
    static String barrier_path = "/curator_recipes_barrier_path";
    static DistributedBarrier barrier;

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        CuratorFramework client = CuratorFrameworkFactory.builder()
                                .connectString("127.0.0.1:2181")
                                .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
                        client.start();
                        barrier = new DistributedBarrier(client, barrier_path);
                        System.out.println(Thread.currentThread().getName() + "号barrier設定");
                        barrier.setBarrier();
                        barrier.waitOnBarrier();
                        System.err.println("啟動...");
                    } catch (Exception e) {
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
        barrier.removeBarrier();
    }
}
           

4.4.3、Curator工具類

4.4.3.1 ZKPaths

其提供了簡單的API來建構znode路徑、遞歸建立、删除節點等。

4.4.3.2 EnsurePath

其提供了一種能夠確定資料節點存在的機制,當上層業務希望對一個資料節點進行操作時,操作前需要確定該節點存在。

EnsurePath采取了如下節點建立方式,試圖建立指定節點,如果節點已經存在,那麼就不進行任何操作,也不對外抛出異常,否則正常建立資料節點。

五、Zookeeper應用場景

5.1、典型應用場景及實作

Zookeeper是一個高可用的分布式資料管理和協調架構,并且能夠很好的保證分布式環境中資料的一緻性。在越來越多的分布式系統(Hadoop、HBase、Kafka)中,Zookeeper都作為核心元件使用。

典型應用場景

  • 資料釋出/訂閱
  • 負載均衡
  • 命名服務
  • 分布式協調/通知
  • 叢集管理
  • Master選舉
  • 分布式鎖
  • 分布式隊列

5.1.1 資料釋出/訂閱

資料釋出/訂閱系統,即配置中心。需要釋出者将資料釋出到Zookeeper的節點上,供訂閱者進行資料訂閱,進而達到動态擷取資料的目的,實作配置資訊的集中式管理和資料的動态更新。釋出/訂閱一般有兩種設計模式:推模式和拉模式,服務端主動将資料更新發送給所有訂閱的用戶端稱為推模式;用戶端主動請求擷取最新資料稱為拉模式,Zookeeper采用了推拉相結合的模式,用戶端向服務端注冊自己需要關注的節點,一旦該節點資料發生變更,那麼服務端就會向相應的用戶端推送Watcher事件通知,用戶端接收到此通知後,主動到服務端擷取最新的資料。

若将配置資訊存放到Zookeeper上進行集中管理,在通常情況下,應用在啟動時會主動到Zookeeper服務端上進行一次配置資訊的擷取,同時,在指定節點上注冊一個Watcher監聽,這樣在配置資訊發生變更,服務端都會實時通知所有訂閱的用戶端,進而達到實時擷取最新配置的目的。

5.1.2 負載均衡

負載均衡是一種相當常見的計算機網絡技術,用來對多個計算機、網絡連接配接、CPU、磁盤驅動或其他資源進行配置設定負載,以達到優化資源使用、最大化吞吐率、最小化響應時間和避免過載的目的。

使用Zookeeper實作動态DNS服務

· 域名配置,首先在Zookeeper上建立一個節點來進行域名配置,如DDNS/app1/server.app1.company1.com。

· 域名解析,應用首先從域名節點中擷取IP位址和端口的配置,進行自行解析。同時,應用程式還會在域名節點上注冊一個資料變更Watcher監聽,以便及時收到域名變更的通知。

· 域名變更,若發生IP或端口号變更,此時需要進行域名變更操作,此時,隻需要對指定的域名節點進行更新操作,Zookeeper就會向訂閱的用戶端發送這個事件通知,用戶端之後就再次進行域名配置的擷取。

5.1.3 命名服務

命名服務是分步實作系統中較為常見的一類場景,分布式系統中,被命名的實體通常可以是叢集中的機器、提供的服務位址或遠端對象等,通過命名服務,用戶端可以根據指定名字來擷取資源的實體、服務位址和提供者的資訊。Zookeeper也可幫助應用系統通過資源引用的方式來實作對資源的定位和使用,廣義上的命名服務的資源定位都不是真正意義上的實體資源,在分布式環境中,上層應用僅僅需要一個全局唯一的名字。Zookeeper可以實作一套分布式全局唯一ID的配置設定機制。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

5.1.4 分布式協調/通知

Zookeeper中特有的Watcher注冊于異步通知機制,能夠很好地實作分布式環境下不同機器,甚至不同系統之間的協調與通知,進而實作對資料變更的實時處理。通常的做法是不同的用戶端都對Zookeeper上的同一個資料節點進行Watcher注冊,監聽資料節點的變化(包括節點本身和子節點),若資料節點發生變化,那麼所有訂閱的用戶端都能夠接收到相應的Watcher通知,并作出相應處理。

MySQL資料複制總線是一個實時的資料複制架構,用于在不同的MySQL資料庫執行個體之間進行異步資料複制和資料變化通知,整個系統由MySQL資料庫叢集、消息隊列系統、任務管理監控平台、Zookeeper叢集等元件共同構成的一個包含生産者、複制管道、資料消費等部分的資料總線系統。

Zookeeper主要負責進行分布式協調工作,在具體的實作上,根據功能将資料複制元件劃分為三個子產品:Core(實作資料複制核心邏輯,将資料複制封裝成管道,并抽象出生産者和消費者概念)、Server(啟動和停止複制任務)、Monitor(監控任務的運作狀态,若資料複制期間發生異常或出現故障則進行告警)

在絕大多數分布式系統中,系統機器間的通信無外乎心跳檢測、工作進度彙報和系統排程。

① 心跳檢測,不同機器間需要檢測到彼此是否在正常運作,可以使用Zookeeper實作機器間的心跳檢測,基于其臨時節點特性(臨時節點的生存周期是用戶端會話,用戶端若當即後,其臨時節點自然不再存在),可以讓不同機器都在Zookeeper的一個指定節點下建立臨時子節點,不同的機器之間可以根據這個臨時子節點來判斷對應的用戶端機器是否存活。通過Zookeeper可以大大減少系統耦合。

② 工作進度彙報,通常任務被分發到不同機器後,需要實時地将自己的任務執行進度彙報給分發系統,可以在Zookeeper上選擇一個節點,每個任務用戶端都在這個節點下面建立臨時子節點,這樣不僅可以判斷機器是否存活,同時各個機器可以将自己的任務執行進度寫到該臨時節點中去,以便中心系統能夠實時擷取任務的執行進度。

③ 系統排程,Zookeeper能夠實作如下系統排程模式:分布式系統由控制台和一些用戶端系統兩部分構成,控制台的職責就是需要将一些指令資訊發送給所有的用戶端,以控制他們進行相應的業務邏輯,背景管理人員在控制台上做一些操作,實際上就是修改Zookeeper上某些節點的資料,Zookeeper可以把資料變更以時間通知的形式發送給訂閱用戶端。

5.1.5 叢集管理

Zookeeper的兩大特性:

  • 用戶端如果對Zookeeper的資料節點注冊Watcher監聽,那麼當該資料及該單内容或是其子節點清單發生變更時,Zookeeper伺服器就會向訂閱的用戶端發送變更通知。
  • 對在Zookeeper上建立的臨時節點,一旦用戶端與伺服器之間的會話失效,那麼臨時節點也會被自動删除。
  • 變化的日志源機器
  • 變化的收集器機器

5.1.6 Master選舉

在分布式系統中,Master往往用來協調叢集中其他系統單元,具有對分布式系統狀态變更的決定權,如在讀寫分離的應用場景中,用戶端的寫請求往往是由Master來處理,或者其常常處理一些複雜的邏輯并将處理結果同步給其他系統單元。利用Zookeeper的強一緻性,能夠很好地保證在分布式高并發情況下節點的建立一定能夠保證全局唯一性,即Zookeeper将會保證用戶端無法重複建立一個已經存在的資料節點。

首先建立/master_election/2016-11-12節點,用戶端叢集每天會定時往該節點下建立臨時節點,如/master_election/2016-11-12/binding,這個過程中,隻有一個用戶端能夠成功建立,此時其變成master,其他節點都會在節點/master_election/2016-11-12上注冊一個子節點變更的Watcher,用于監控目前的Master機器是否存活,一旦發現目前Master挂了,其餘用戶端将會重新進行Master選舉。

5.1.7 分布式鎖

分布式鎖用于控制分布式系統之間同步通路共享資源的一種方式,可以保證不同系統通路一個或一組資源時的一緻性,主要分為排它鎖和共享鎖。

排它鎖又稱為寫鎖或獨占鎖,若事務T1對資料對象O1加上了排它鎖,那麼在整個加鎖期間,隻允許事務T1對O1進行讀取和更新操作,其他任何事務都不能再對這個資料對象進行任何類型的操作,直到T1釋放了排它鎖。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

① 擷取鎖,在需要擷取排它鎖時,所有用戶端通過調用接口,在/exclusive_lock節點下建立臨時子節點/exclusive_lock/lock。Zookeeper可以保證隻有一個用戶端能夠建立成功,沒有成功的用戶端需要注冊/exclusive_lock節點監聽。

② 釋放鎖,當擷取鎖的用戶端當機或者正常完成業務邏輯都會導緻臨時節點的删除,此時,所有在/exclusive_lock節點上注冊監聽的用戶端都會收到通知,可以重新發起分布式鎖擷取。

共享鎖又稱為讀鎖,若事務T1對資料對象O1加上共享鎖,那麼目前事務隻能對O1進行讀取操作,其他事務也隻能對這個資料對象加共享鎖,直到該資料對象上的所有共享鎖都被釋放。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

① 擷取鎖,在需要擷取共享鎖時,所有用戶端都會到/shared_lock下面建立一個臨時順序節點,如果是讀請求,那麼就建立例如/shared_lock/host1-R-00000001的節點,如果是寫請求,那麼就建立例如/shared_lock/host2-W-00000002的節點。

② 判斷讀寫順序,不同僚務可以同時對一個資料對象進行讀寫操作,而更新操作必須在目前沒有任何事務進行讀寫情況下進行,通過Zookeeper來确定分布式讀寫順序,大緻分為四步。

1. 建立完節點後,擷取/shared_lock節點下所有子節點,并對該節點變更注冊監聽。

2. 确定自己的節點序号在所有子節點中的順序。

3. 對于讀請求:若沒有比自己序号小的子節點或所有比自己序号小的子節點都是讀請求,那麼表明自己已經成功擷取到共享鎖,同時開始執行讀取邏輯,若有寫請求,則需要等待。對于寫請求:若自己不是序号最小的子節點,那麼需要等待。

4. 接收到Watcher通知後,重複步驟1。

③ 釋放鎖,其釋放鎖的流程與獨占鎖一緻。

上述共享鎖的實作方案,可以滿足一般分布式叢集競争鎖的需求,但是如果機器規模擴大會出現一些問題,下面着重分析判斷讀寫順序的步驟3。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

針對如上圖所示的情況進行分析

1. host1首先進行讀操作,完成後将節點/shared_lock/host1-R-00000001删除。

2. 餘下4台機器均收到這個節點移除的通知,然後重新從/shared_lock節點上擷取一份新的子節點清單。

3. 每台機器判斷自己的讀寫順序,其中host2檢測到自己序号最小,于是進行寫操作,餘下的機器則繼續等待。

4. 繼續…

可以看到,host1用戶端在移除自己的共享鎖後,Zookeeper發送了子節點更變Watcher通知給所有機器,然而除了給host2産生影響外,對其他機器沒有任何作用。大量的Watcher通知和子節點清單擷取兩個操作會重複運作,這樣會造成系能鞥影響和網絡開銷,更為嚴重的是,如果同一時間有多個節點對應的用戶端完成事務或事務中斷引起節點小時,Zookeeper伺服器就會在短時間内向其他所有用戶端發送大量的事件通知,這就是所謂的羊群效應。

可以有如下改動來避免羊群效應。

1. 用戶端調用create接口常見類似于/shared_lock/[Hostname]-請求類型-序号的臨時順序節點。

2. 用戶端調用getChildren接口擷取所有已經建立的子節點清單(不注冊任何Watcher)。

3. 如果無法擷取共享鎖,就調用exist接口來對比自己小的節點注冊Watcher。對于讀請求:向比自己序号小的最後一個寫請求節點注冊Watcher監聽。對于寫請求:向比自己序号小的最後一個節點注冊Watcher監聽。

4. 等待Watcher通知,繼續進入步驟2。

此方案改動主要在于:每個鎖競争者,隻需要關注/shared_lock節點下序号比自己小的那個節點是否存在即可。

5.1.8 分布式隊列

分布式隊列可以簡單分為先入先出隊列模型和等待隊列元素聚集後統一安排處理執行的Barrier模型。

① FIFO先入先出,先進入隊列的請求操作先完成後,才會開始處理後面的請求。FIFO隊列就類似于全寫的共享模型,所有用戶端都會到/queue_fifo這個節點下建立一個臨時節點,如/queue_fifo/host1-00000001。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

建立完節點後,按照如下步驟執行。

1. 通過調用getChildren接口來擷取/queue_fifo節點的所有子節點,即擷取隊列中所有的元素。

2. 确定自己的節點序号在所有子節點中的順序。

3. 如果自己的序号不是最小,那麼需要等待,同時向比自己序号小的最後一個節點注冊Watcher監聽。

4. 接收到Watcher通知後,重複步驟1。

② Barrier分布式屏障,最終的合并計算需要基于很多并行計算的子結果來進行,開始時,/queue_barrier節點已經預設存在,并且将結點資料内容指派為數字n來代表Barrier值,之後,所有用戶端都會到/queue_barrier節點下建立一個臨時節點,例如/queue_barrier/host1。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

建立完節點後,按照如下步驟執行。

1. 通過調用getData接口擷取/queue_barrier節點的資料内容,如10。

2. 通過調用getChildren接口擷取/queue_barrier節點下的所有子節點,同時注冊對子節點變更的Watcher監聽。

3. 統計子節點的個數。

4. 如果子節點個數還不足10個,那麼需要等待。

5. 接受到Wacher通知後,重複步驟3。

5.2、zk在大型分布式系統中的應用

5.2.1 Hadoop

Hadoop的核心是HDFS(Hadoop Distributed File System)和MapReduce,分别提供了對海量資料的存儲和計算能力,後來,Hadoop又引入了全新MapReduce架構YARN(Yet Another Resource Negotiator)。在Hadoop中,Zookeeper主要用于實作HA(High Availability),這部分邏輯主要集中在Hadoop Common的HA子產品中,HDFS的NameNode與YARN的ResourceManager都是基于此HA子產品來實作自己的HA功能,YARN又使用了Zookeeper來存儲應用的運作狀态。

5.2.1.1 YARN

YARN是一種新的 Hadoop 資料總管,它是一個通用資源管理系統,可為上層應用提供統一的資源管理和排程,它的引入為叢集在使用率、資源統一管理和資料共享等方面帶來了巨大好處。其可以支援MapReduce模型,同時也支援Tez、Spark、Storm、Impala、Open MPI等。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

YARN主要由ResourceManager(RM)、NodeManager(NM)、ApplicationManager(AM)、Container四部分構成。其中,ResourceManager為全局資料總管,負責整個系統的資源管理和配置設定。由YARN體系架構可以看到ResourceManager的單點問題,ResourceManager的工作狀況直接決定了整個YARN架構是否可以正常運轉。

5.2.1.2 ResourceManager HA

為了解決ResourceManager的單點問題,YARN設計了一套Active/Standby模式的ResourceManager HA架構。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

由上圖可知,在運作期間,會有多個ResourceManager并存,并且其中隻有一個ResourceManager處于Active狀态,另外一些(允許一個或者多個)則處于Standby狀态,當Active節點無法正常工作時,其餘處于Standby狀态的節點則會通過競争選舉産生新的Active節點。

5.2.1.3 主備切換

ResourceManager使用基于Zookeeper實作的ActiveStandbyElector元件來确定ResourceManager的狀态。具體步驟如下

1. 建立鎖節點。在Zookeeper上會有一個類似于/yarn-leader-election/pseudo-yarn-rm-cluster的鎖節點,所有的ResourceManager在啟動時,都會去競争寫一個Lock子節點(/yarn-leader-election/pseudo-yarn-rm-cluster/ActiveStandbyElectorLock),子節點類型為臨時節點,利用Zookeeper的特性,建立成功的那個ResourceManager切換為Active狀态,其餘的為Standby狀态。

2. 注冊Watcher監聽。所有Standby狀态的ResourceManager都會向/yarn-leader-election/pseudo-yarn-rm-cluster/ActiveStandbyElectorLock節點注冊一個節點變更監聽,利用臨時節點的特性,能夠快速感覺到Active狀态的ResourceManager的運作情況。

3. 主備切換。當Active的ResourceManager無法正常工作時,其建立的Lock節點也會被删除,此時,其餘各個Standby的ResourceManager都會收到通知,然後重複步驟1。

5.2.1.4 隔離(Fencing)

在分布式環境中,經常會出現諸如單機假死(機器由于網絡閃斷或是其自身由于負載過高,常見的有GC占用時間過長或CPU負載過高,而無法正常地對外進行及時響應)情況。假設RM叢集由RM1和RM2兩台機器構成,某一時刻,RM1發生了假死,此時,Zookeeper認為RM1挂了,然後進行主備切換,RM2會成為Active狀态,但是在随後,RM1恢複了正常,其依然認為自己還處于Active狀态,這就是分布式腦裂現象,即存在多個處于Active狀态的RM工作,可以使用隔離來解決此類問題。

YARN引入了Fencing機制,借助Zookeeper的資料節點的ACL權限控制機制來實作不同RM之間的隔離。在上述主備切換時,多個RM之間通過競争建立鎖節點來實作主備狀态的确定,此時,隻需要在建立節點時攜帶Zookeeper的ACL資訊,目的是為了獨占該節點,以防止其他RM對該節點進行更新。

還是上述案例,若RM1出現假死,Zookeeper會移除其建立的節點,此時RM2會建立相應的鎖節點并切換至Active狀态,RM1恢複之後,會試圖去更新Zookeeper相關資料,但是此時其沒有權限更新Zookeeper的相關節點資料,因為節點不是由其建立的,于是就自動切換至Standby狀态,這樣就避免了腦裂現象的出現。

5.2.1.5 ResourceManager狀态存儲

在ResourceManager中,RMStateStore可以存儲一些RM的内部狀态資訊,包括Application以及Attempts資訊、Delegation Token及Version Information等,值得注意的是,RMStateStore的絕大多數狀态資訊都是不需要持久化存儲的(如資源使用情況),因為其很容易從上下文資訊中重構,,在存儲方案設計中,提供了三種可能的實作。

1. 基于記憶體實作,一般用于日常開發測試。

2. 基于檔案系統實作,如HDFS。

3. 基于Zookeeper實作。

由于存儲的資訊不是特别大,Hadoop官方建議基于Zookeeper來實作狀态資訊的存儲,在Zookeeper中,ResourceManager的狀态資訊都被存儲在/rmstore這個根節點下,其資料結構如下。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

在RMAppRoot節點下存儲的是與各個Application相關的資訊,RMDTSecretManagerRoot存儲的是與安全相關的Token資訊。每個Active狀态的ResourceManager在初始化節點都會從Zookeeper上讀取到這些資訊,并根據這些狀态資訊繼續後續的處理。

5.2.2 HBase

省略,如需檢視,請點選

5.2.3 Kafka

kafka是一個吞吐量極高的分布式消息系統,其整體設計是典型的釋出與訂閱系統模式,在Kafka叢集中,沒有中心主節點概念,所有伺服器都是對等的,是以,可以在不做任何配置更改的情況下實作伺服器的添加與删除,同樣,消息的生産者和消費者也能夠随意重新開機和機器的上下線。

生産者(Producer):消息産生的源頭,負責生成消息并發送到Kafka伺服器。

消費者(Consumer):消息的使用方,負責消費Kafka伺服器上的消息。

主題(Topic):由使用者定義并配置在Kafka服務端,用于建立生産者和消費者之間的訂閱關系,生産者發送消息到指定Topic下,消費者從這個Topic中消費消息。

消息分區(Partition):一個Topic下會分為多個分區,如"kafka-test"這個Topic可以分為10個分區,分别由兩台伺服器提供,那麼通常可以配置讓每台伺服器提供5個分區,假設伺服器ID為0和1,那麼分區為0-0、0-1、0-2、0-3、0-4和1-0、 1-1、1-2、1-3、1-4。消息分區機制和分區的數量與消費者的負載均衡機制有很大的關系。

伺服器(Broker):用于存儲資訊,在消息中間件中通常被稱為Broker。

消費者分組(Group):歸組同類消費者,多個消費者可以共同消費一個Topic下的消息,每個消費者消費其中的部分消息,這些消費者組成了消費者分組,擁有同一個分組名稱,通常也被稱為消費者叢集。

偏移量(Offset):消息存儲在Kafka的Broker上,消費者拉取消息資料的過程中需要知道消息在檔案中的偏移量。

5.2.3.1 Broker注冊

Broker是分布式部署并且互相之間互相獨立,但是需要有一個注冊系統能夠将整個叢集中的Broker管理起來,此時就使用到了Zookeeper。在Zookeeper上會有一個專門用來進行Broker伺服器清單記錄的節點/brokers/ids。每個Broker在啟動時,都會到Zookeeper上進行注冊,即到/brokers/ids下建立屬于自己的節點,如/brokers/ids/[0…N]。Kafka使用了全局唯一的數字來指代每個Broker伺服器,不同的Broker必須使用不同的Broker ID進行注冊,建立完節點後,每個Broker就會将自己的IP位址和端口資訊記錄到該節點中去。其中,Broker建立的節點類型是臨時節點,一旦Broker當機,則對應的臨時節點也會被自動删除。

5.2.3.2 Topic注冊

在Kafka中,同一個Topic的消息會被分成多個分區并将其分布在多個Broker上,這些分區資訊及與Broker的對應關系也都是由Zookeeper在維護,由專門的節點來記錄,如/borkers/topics。Kafka中每個Topic都會以/brokers/topics/[topic]的形式被記錄,如/brokers/topics/login和/brokers/topics/search等。Broker伺服器啟動後,會到對應Topic節點(/brokers/topics)上注冊自己的Broker ID并寫入針對該Topic的分區總數,如/brokers/topics/login/3->2,這個節點表示Broker ID為3的一個Broker伺服器,對于"login"這個Topic的消息,提供了2個分區進行消息存儲,同樣,這個分區節點也是臨時節點。

5.2.3.3 生産者負載均衡

由于同一個Topic消息會被分區并将其分布在多個Broker上,是以,生産者需要将消息合理地發送到這些分布式的Broker上,那麼如何實作生産者的負載均衡,Kafka支援傳統的四層負載均衡,也支援Zookeeper方式實作負載均衡。

① 四層負載均衡,根據生産者的IP位址和端口來為其确定一個相關聯的Broker。通常,一個生産者隻會對應單個Broker,然後該生産者産生的消息都發往該Broker。這種方式邏輯簡單,每個生産者不需要同其他系統建立額外的TCP連接配接,隻需要和Broker維護單個TCP連接配接即可。但是,其無法做到真正的負載均衡,因為實際系統中的每個生産者産生的消息量及每個Broker的消息存儲量都是不一樣的,如果有些生産者産生的消息遠多于其他生産者的話,那麼會導緻不同的Broker接收到的消息總數差異巨大,同時,生産者也無法實時感覺到Broker的新增和删除。

② 使用Zookeeper進行負載均衡,由于每個Broker啟動時,都會完成Broker注冊過程,生産者會通過該節點的變化來動态地感覺到Broker伺服器清單的變更,這樣就可以實作動态的負載均衡機制。

5.2.3.4 消費者負載均衡

與生産者類似,Kafka中的消費者同樣需要進行負載均衡來實作多個消費者合理地從對應的Broker伺服器上接收消息,每個消費者分組包含若幹消費者,每條消息都隻會發送給分組中的一個消費者,不同的消費者分組消費自己特定的Topic下面的消息,互不幹擾。

5.2.3.5 消費分區與消費者的關系

對于每個消費者分組,Kafka都會為其配置設定一個全局唯一的Group ID,同一個消費者分組内部的所有消費者共享該ID。同時,Kafka為每個消費者配置設定一個Consumer ID,通常采用"Hostname:UUID"形式表示。在Kafka中,規定了每個消息分區有且隻能同時有一個消費者進行消費,是以,需要在Zookeeper上記錄消息分區與消費者之間的關系,每個消費者一旦确定了對一個消息分區的消費權力,需要将其Consumer ID 寫入到對應消息分區的臨時節點上,例如/consumers/[group_id]/owners/[topic]/[broker_id-partition_id],其中,[broker_id-partition_id]就是一個消息分區的辨別,節點内容就是該消費分區上消息消費者的Consumer ID。

5.2.3.6 消息消費進度Offset記錄

在消費者對指定消息分區進行消息消費的過程中,需要定時地将分區消息的消費進度Offset記錄到Zookeeper上,以便在該消費者進行重新開機或者其他消費者重新接管該消息分區的消息消費後,能夠從之前的進度開始繼續進行消息消費。Offset在Zookeeper中由一個專門節點進行記錄,其節點路徑為/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id],節點内容就是Offset的值。

5.2.3.7 消費者注冊

消費者伺服器在初始化啟動時加入消費者分組的步驟如下

① 注冊到消費者分組。每個消費者伺服器啟動時,都會到Zookeeper的指定節點下建立一個屬于自己的消費者節點,例如/consumers/[group_id]/ids/[consumer_id],完成節點建立後,消費者就會将自己訂閱的Topic資訊寫入該臨時節點。

② 對消費者分組中的消費者的變化注冊監聽。每個消費者都需要關注所屬消費者分組中其他消費者伺服器的變化情況,即對/consumers/[group_id]/ids節點注冊子節點變化的Watcher監聽,一旦發現消費者新增或減少,就觸發消費者的負載均衡。

③ 對Broker伺服器變化注冊監聽。消費者需要對/broker/ids/[0-N]中的節點進行監聽,如果發現Broker伺服器清單發生變化,那麼就根據具體情況來決定是否需要進行消費者負載均衡。

④ 進行消費者負載均衡。為了讓同一個Topic下不同分區的消息盡量均衡地被多個消費者消費而進行消費者與消息分區配置設定的過程,通常,對于一個消費者分組,如果組内的消費者伺服器發生變更或Broker伺服器發生變更,會發出消費者負載均衡。

5.2.3.8 負載均衡

Kafka借助Zookeeper上記錄的Broker和消費者資訊,采用消費者均衡算法進行負載均衡,其具體步驟如下。假設一個消息分組的每個消費者記為C1,C2,Ci,…,Cn。那麼對于消費者Ci,其對應的消息分區配置設定政策如下:

1. 設定Pr為指定Topic所有的消息分區。

2. 設定Cg為統一消費者分組中的所有消費者。

3. 對Pr進行排序,使分布在同一個Broker伺服器上的分區盡量靠在一起。

4. 對Cg進行排序。

5. 設定i為Ci在Cg中的位置索引,同時設定N = size (Pr) / size (Cg)。

6. 将編号為i * N ~ (i + 1) * N - 1的消息分區配置設定給Ci。

7. 重新更新Zookeeper上消息分區與消費者Ci的關系。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記

第6章ZooKeeper技術内幕

6.1 系統模型

6.1.1 資料模型

Zookeeper的資料節點稱為ZNode,ZNode是Zookeeper中資料的最小單元,每個ZNode都可以儲存資料,同時還可以挂載子節點,是以構成了一個階層化的命名空間,稱為樹。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

在Zookeeper中,事務是指能夠改變Zookeeper伺服器狀态的操作,一般包括節點建立與删除,資料節點内容更新和用戶端會話建立與失效,對于每個事務請求,Zookeeper都會為其配置設定一個全局唯一的事務ID,用ZXID表示,通常是64位的數字,每個ZXID對應一次更新操作,從這些ZXID中可以間接地識别出Zookeeper處理這些更新操作請求的全局順序。

6.1.2 節點特性

在Zookeeper中,每個資料節點都是由生命周期的,類型不同則會不同的生命周期,節點類型可以分為持久節點(PERSISTENT)、臨時節點(EPHEMERAL)、順序節點(SEQUENTIAL)三大類,可以通過組合生成如下四種類型節點

1. 持久節點(PERSISTENT)。節點建立後便一直存在于Zookeeper伺服器上,直到有删除操作來主動清楚該節點。

2. 持久順序節點(PERSISTENT_SEQUENTIAL)。相比持久節點,其新增了順序特性,每個父節點都會為它的第一級子節點維護一份順序,用于記錄每個子節點建立的先後順序。在建立節點時,會自動添加一個數字字尾,作為新的節點名,該數字字尾的上限是整形的最大值。

3. 臨時節點(EPEMERAL)。臨時節點的生命周期與用戶端會話綁定,用戶端失效,節點會被自動清理。同時,Zookeeper規定不能基于臨時節點來建立子節點,即臨時節點隻能作為葉子節點。

4. 臨時順序節點(EPEMERAL_SEQUENTIAL)。在臨時節點的基礎添加了順序特性。

每個節點除了存儲資料外,還存儲了節點本身的一些狀态資訊,可通過get指令擷取。

6.1.3 版本——保證分布式資料原子性操作

每個資料節點都具有三種類型的版本資訊,對資料節點的任何更新操作都會引起版本号的變化。

version-- 目前資料節點資料内容的版本号

cversion-- 目前資料子節點的版本号

aversion-- 目前資料節點ACL變更版本号

上述各版本号都是表示修改次數,如version為1表示對資料節點的内容變更了一次。即使前後兩次變更并沒有改變資料内容,version的值仍然會改變。version可以用于寫入驗證,類似于CAS。

6.1.4 Watcher——資料變更的通知

Zookeeper使用Watcher機制實作分布式資料的釋出/訂閱功能。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

Zookeeper的Watcher機制主要包括用戶端線程、用戶端WatcherManager、Zookeeper伺服器三部分。用戶端在向Zookeeper伺服器注冊的同時,會将Watcher對象存儲在用戶端的WatcherManager當中。當Zookeeper伺服器觸發Watcher事件後,會向用戶端發送通知,用戶端線程從WatcherManager中取出對應的Watcher對象來執行回調邏輯。

6.1.5 ACL——保障資料的安全

Zookeeper内部存儲了分布式系統運作時狀态的中繼資料,這些中繼資料會直接影響基于Zookeeper進行構造的分布式系統的運作狀态,如何保障系統中資料的安全,進而避免因誤操作而帶來的資料随意變更而導緻的資料庫異常十分重要,Zookeeper提供了一套完善的ACL權限控制機制來保障資料的安全。

我們可以從三個方面來了解ACL機制:權限模式(Scheme)、授權對象(ID)、權限(Permission),通常使用"**scheme : id : permission **"來辨別一個有效的ACL資訊。

權限模式用來确定權限驗證過程中使用的檢驗政策,有如下四種模式:

1. IP,通過IP位址粒度來進行權限控制,如"ip:192.168.0.110"表示權限控制針對該IP位址,同時IP模式可以支援按照網段方式進行配置,如"ip:192.168.0.1/24"表示針對192.168.0.*這個網段進行權限控制。

2. Digest,使用"username:password"形式的權限辨別來進行權限配置,便于區分不同應用來進行權限控制。Zookeeper會對其進行SHA-1加密和BASE64編碼。

3. World,最為開放的權限控制模式,資料節點的通路權限對所有使用者開放。

4. Super,超級使用者,是一種特殊的Digest模式,超級使用者可以對任意Zookeeper上的資料節點進行任何操作。

授權對象是指權限賦予的使用者或一個指定實體,如IP位址或機器等。不同的權限模式通常有不同的授權對象。

權限是指通過權限檢查可以被允許執行的操作,Zookeeper對所有資料的操作權限分為CREATE(節點建立權限)、DELETE(節點删除權限)、READ(節點讀取權限)、WRITE(節點更新權限)、ADMIN(節點管理權限),這5種權限簡寫為crwda。

自定義權限控制

權限控制器需要實作AuthenticationProvider接口,注冊自定義權限控制器通過在zoo.cfg配置檔案中配置如下配置項:

authProvider.1=com.zkbook.CustomAuthenticationProvider

6.2 序列化與協定

Zookeeper的用戶端與服務端之間會進行一系列的網絡通信來實作資料傳輸,Zookeeper使用Jute元件來完成資料的序列化和反序列化操作。

6.2.1 Jute介紹

Jute是Zookeeper底層序列化元件,其用于Zookeeper進行網絡資料傳輸和本地磁盤資料存儲的序列化和反序列化工作。

在Zookeeper的src檔案夾下有zookeeper.jute檔案,定義了所有的實體類的所屬包名、類名及類的所有成員變量和類型,該檔案會在源代碼編譯時,Jute會使用不同的代碼生成器為這些類定義生成實際程式設計語言的類檔案,如java語言生成的類檔案儲存在src/java/generated目錄下,每個類都會實作Record接口。

6.2.2 使用Jute進行序列化

6.2.3 深入Jute

6.2.4 通信協定

基于TCP/IP協定,Zookeeper實作了自己的通信協定來玩按成用戶端與服務端、服務端與服務端之間的網絡通信,對于請求,主要包含請求頭和請求體,對于響應,主要包含響應頭和響應體。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

6.2.4.1 請求協定

對于請求協定而言,如下為擷取節點資料請求的完整協定定義

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference
class RequestHeader {
        int xid;
        int type;
    }
           

從zookeeper.jute中可知RequestHeader包含了xid和type,xid用于記錄用戶端請求發起的先後序号,用來確定單個用戶端請求的響應順序,type代表請求的操作類型,如建立節點(OpCode.create)、删除節點(OpCode.delete)、擷取節點資料(OpCode.getData)。

協定的請求主體内容部分,包含了請求的所有操作内容,不同的請求類型請求體不同。對于會話建立而言,其請求體如下

class ConnectRequest {
    int protocolVersion;
    long lastZxidSeen;
    int timeOut;
    long sessionId;
    buffer passwd;
}
           

Zookeeper用戶端和伺服器在建立會話時,會發送ConnectRequest請求,該請求包含協定版本号protocolVersion、最近一次接收到伺服器ZXID lastZxidSeen、會話逾時時間timeOut、會話辨別sessionId和會話密碼passwd。

  對于擷取節點資料而言,其請求體如下

class GetDataRequest {
    ustring path;
    boolean watch;
}
           

Zookeeper用戶端在向伺服器發送節點資料請求時,會發送GetDataRequest請求,該請求包含了資料節點路徑path、是否注冊Watcher的辨別watch。

對于更新節點資料而言,其請求體如下

class SetDataRequest {
    ustring path;
    buffer data;
    int version;
}
           

Zookeeper用戶端在向伺服器發送更新節點資料請求時,會發送SetDataRequest請求,該請求包含了資料節點路徑path、資料内容data、節點資料的期望版本号version。

針對不同的請求類型,Zookeeper都會定義不同的請求體,可以在zookeeper.jute中檢視。

6.2.4.2 響應協定

對于響應協定而言,如下為擷取節點資料響應的完整協定定義

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

響應頭中包含了每個響應最基本的資訊,包括xid、zxid和err:

class ReplyHeader {
    int xid;
    long zxid;
    int err;
}
           

xid與請求頭中的xid一緻,zxid表示Zookeeper伺服器上目前最新的事務ID,err則是一個錯誤碼,表示當請求處理過程出現異常情況時,就會在錯誤碼中辨別出來,常見的包括處理成功(Code.OK)、節點不存在(Code.NONODE)、沒有權限(Code.NOAUTH)。

協定的響應主體内容部分,包含了響應的所有資料,不同的響應類型請求體不同。對于會話建立而言,其響應體如下

class ConnectResponse {
    int protocolVersion;
    int timeOut;
    long sessionId;
    buffer passwd;
}
           

針對用戶端的會話建立請求,服務端會傳回用戶端一個ConnectResponse響應,該響應體包含了版本号protocolVersion、會話的逾時時間timeOut、會話辨別sessionId和會話密碼passwd。

對于擷取節點資料而言,其響應體如下

class GetDataResponse {
    buffer data;
    org.apache.zookeeper.data.Stat stat;
}
           

針對用戶端的擷取節點資料請求,服務端會傳回用戶端一個GetDataResponse響應,該響應體包含了資料節點内容data、節點狀态stat。

對于更新節點資料而言,其響應體如下

class SetDataResponse {
    org.apache.zookeeper.data.Stat stat;
}
           

針對用戶端的更新節點資料請求,服務端會傳回用戶端一個SetDataResponse響應,該響應體包含了最新的節點狀态stat。

針對不同的響應類型,Zookeeper都會定義不同的響應體,可以在zookeeper.jute中檢視。

6.2.5 stat狀态說明

stat對象狀态屬性說明:

1、czxid:即created zxid,表示該資料節點被建立時的事務id
	2、mzxid:即modified zxid,表示該節點最後一次被更新時的事務id
	3、ctime:即created time,表示節點被建立的時間
	4、mtime:即modified time,表示該節點最後一次被更新的時間
	5、version:資料節點的版本号
	6、cversion:子節點的版本号
	7、aversion:節點的acl版本号
	8、ephemeralOwner:建立該臨時節點的會話的sessionid,如果該節點是持久節點,那麼這個屬性值為0
	9、dataLength:資料内容的長度
	10、numChildren:目前節點的子節點個數
	11、pzxid:表示該節點的子節點清單最後一次被修改時的事務id,注意,隻有子節點清單變更了才會變更pzxid,子節點内容變更不會影響pzxid
           

在一個資料節點/zk-book被建立完畢後,節點的version值是0,表示的含義是“目前節點自從建立之後,被更新過0次”。如果現在對該節點的資料内容

進行更新操作,那麼随後,version值就會變成1,同時需要注意的是,其表示的是對資料節點資料内容的變更次數,強調的是變更次數,是以即使前後

兩次變更并沒有使得資料内容的值發生變化,version的值任然會變更。

6.3 用戶端

用戶端是開發人員使用Zookeeper最主要的途徑,很有必要弄懂用戶端是如何與服務端通信的。

Zookeeper用戶端主要由如下核心部件構成。

1. Zookeeper執行個體,用戶端入口。

2. ClientWatchManager, 用戶端Watcher管理器。

3. HostProvider,用戶端位址清單管理器。

4. ClientCnxn,用戶端核心線程,内部包含了SendThread和EventThread兩個線程,SendThread為I/O線程,主要負責Zookeeper用戶端和伺服器之間的網絡I/O通信;EventThread為事件線程,主要負責對服務端事件進行處理。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

Zookeeper用戶端初始化與啟動環節,就是Zookeeper對象的執行個體化過程。用戶端在初始化和啟動過程中大體可以分為如下3個步驟

1. 設定預設Watcher

2. 設定Zookeeper伺服器位址清單

3. 建立ClientCnxn。

若在Zookeeper構造方法中傳入Watcher對象時,那麼Zookeeper就會将該Watcher對象儲存在ZKWatcherManager的defaultWatcher中,并作為整個用戶端會話期間的預設Watcher。

6.3.1 一次會話的建立過程

下圖表示了用戶端與服務端會話建立的整個過程,包括初始化階段(第一階段)、會話建立階段(第二階段)、響應處理階段(第三階段)三個階段。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

6.3.2 伺服器位址清單

在執行個體化Zookeeper時,使用者傳入Zookeeper伺服器位址清單,如192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181,此時,Zookeeper用戶端在連接配接伺服器的過程中,是如何從這個伺服器清單中選擇伺服器的呢?Zookeeper收到伺服器位址清單後,會解析出chrootPath和儲存伺服器位址清單。

1. Chroot,每個用戶端可以設定自己的命名空間,若用戶端設定了Chroot,此時,該用戶端對伺服器的任何操作都将被限制在自己的命名空間下,如設定Choot為/app/X,那麼該用戶端的所有節點路徑都是以/app/X為根節點。

2. 位址清單管理,Zookeeper使用StaticHostProvider打散伺服器位址(shuffle),并将伺服器位址形成一個環形循環隊列,然後再依次取出伺服器位址。

6.3.3 ClientCnxn:網絡I/O

ClientCnxn是Zookeeper用戶端中負責維護用戶端與服務端之間的網絡連接配接并進行一系列網絡通信的核心工作類,Packet是ClientCnxn内部定義的一個堆協定層的封裝,用作Zookeeper中請求和響應的載體。Packet包含了請求頭(requestHeader)、響應頭(replyHeader)、請求體(request)、響應體(response)、節點路徑(clientPath/serverPath)、注冊的Watcher(watchRegistration)等資訊,然而,并非Packet中所有的屬性都在用戶端與服務端之間進行網絡傳輸,隻會将requestHeader、request、readOnly三個屬性序列化,并生成可用于底層網絡傳輸的ByteBuffer,其他屬性都儲存在用戶端的上下文中,不會進行與服務端之間的網絡傳輸。

ClientCnxn維護着 outgoingQueue(用戶端的請求發送隊列 和 pendingQueue(服務端響應的等待隊列),outgoingQueue專門用于存儲那些需要發送到服務端的Packet集合,pendingQueue用于存儲那些已經從用戶端發送到服務端的,但是需要等待服務端響應的Packet集合。

在正常情況下,會從outgoingQueue中取出一個可發送的Packet對象,同時生成一個用戶端請求序号XID并将其設定到Packet請求頭中去,然後序列化後再發送,請求發送完畢後,會立即将該Packet儲存到pendingQueue中,以便等待服務端響應傳回後進行相應的處理。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

用戶端擷取到來自服務端的完整響應資料後,根據不同的用戶端請求類型,會進行不同的處理。

1. 若檢測到此時用戶端尚未進行初始化,那麼說明目前用戶端與服務端之間正在進行會話建立,直接将接收的ByteBuffer序列化成ConnectResponse對象。

2. 若目前用戶端已經處于正常會話周期,并且接收到服務端響應是一個事件,那麼将接收的ByteBuffer序列化成WatcherEvent對象,并将該事件放入待處理隊列中。

3. 若是一個正常請求(Create、GetData、Exist等),那麼從pendingQueue隊列中取出一個Packet來進行相應處理。首先會檢驗響應中的XID來確定請求處理的順序性,然後再将接收到的ByteBuffer序列化成Response對象。

SendThread是用戶端ClientCnxn内部的一個核心I/O排程線程,用于管理用戶端與服務端之間的所有網絡I/O操作,在Zookeeper用戶端實際運作中,SendThread的作用如下

1. 維護了用戶端與服務端之間的會話生命周期(通過一定周期頻率内向服務端發送PING包檢測心跳),如果會話周期内用戶端與服務端出現TCP連接配接斷開,那麼就會自動且透明地完成重連操作。

2. 管理了用戶端所有的請求發送和響應接收操作,其将上層用戶端API操作轉換成相應的請求協定并發送到服務端,并完成對同步調用的傳回和異步調用的回調。

3. 将來自服務端的事件傳遞給EventThread去處理。

EventThread是用戶端ClientCnxn内部的一個事件處理線程,負責用戶端的事件處理,并觸發用戶端注冊的Watcher監聽。EventThread中的watingEvents隊列用于臨時存放那些需要被觸發的Object,包括用戶端注冊的Watcher和異步接口中注冊的回調器AsyncCallback。同時,EventThread會不斷地從watingEvents中取出Object,識别具體類型(Watcher或AsyncCallback),并分别調用process和processResult接口方法來實作對事件的觸發和回調。

6.4 會話

用戶端與服務端之間任何互動操作都與會話息息相關,如臨時節點的生命周期、用戶端請求的順序執行、Watcher通知機制等。Zookeeper的連接配接與會話就是用戶端通過執行個體化Zookeeper對象來實作用戶端與服務端建立并保持TCP連接配接的過程.

6.4.1 會話狀态

在Zookeeper用戶端與服務端成功完成連接配接建立後,就建立了一個會話,Zookeeper會話在整個運作期間的生命周期中,會在不同的會話狀态中之間進行切換,這些狀态可以分為CONNECTING、CONNECTED、RECONNECTING、RECONNECTED、CLOSE等。

一旦用戶端開始建立Zookeeper對象,那麼用戶端狀态就會變成CONNECTING狀态,同時用戶端開始嘗試連接配接服務端,連接配接成功後,用戶端狀态變為CONNECTED,通常情況下,由于斷網或其他原因,用戶端與服務端之間會出現斷開情況,一旦碰到這種情況,Zookeeper用戶端會自動進行重連服務,同時用戶端狀态再次變成CONNCTING,直到重新連上服務端後,狀态又變為CONNECTED,在通常情況下,用戶端的狀态總是介于CONNECTING和CONNECTED之間。但是,如果出現諸如會話逾時、權限檢查或是用戶端主動退出程式等情況,用戶端的狀态就會直接變更為CLOSE狀态。

6.4.2 會話建立

Session是Zookeeper中的會話實體,代表了一個用戶端會話,其包含了如下四個屬性

1. sessionID。會話ID,唯一辨別一個會話,每次用戶端建立新的會話時,Zookeeper都會為其配置設定一個全局唯一的sessionID。

2. TimeOut。會話逾時時間,用戶端在構造Zookeeper執行個體時,會配置sessionTimeout參數用于指定會話的逾時時間,Zookeeper用戶端向服務端發送這個逾時時間後,服務端會根據自己的逾時時間限制最終确定會話的逾時時間。

3. TickTime。下次會話逾時時間點,為了便于Zookeeper對會話實行"分桶政策"管理,同時為了高效低耗地實作會話的逾時檢查與清理,Zookeeper會為每個會話标記一個下次會話逾時時間點,其值大緻等于目前時間加上TimeOut。

4. isClosing。标記一個會話是否已經被關閉,當服務端檢測到會話已經逾時失效時,會将該會話的isClosing标記為"已關閉",這樣就能確定不再處理來自該會話的心情求了。

Zookeeper為了保證請求會話的全局唯一性,在SessionTracker初始化時,調用initializeNextSession方法生成一個sessionID,之後在Zookeeper運作過程中,會在該sessionID的基礎上為每個會話進行配置設定,初始化算法如下

public static long initializeNextSession(long id) {
  long nextSid = 0;
  // 無符号右移8位使為了避免左移24後,再右移8位出現負數而無法通過高8位确定sid值
  nextSid = (System.currentTimeMillis() << 24) >>> 8;
  nextSid = nextSid | (id << 56);
  return nextSid;
}
           

其中的id表示配置在myid檔案中的值,通常是一個整數,如1、2、3。該算法的高8位确定了所在機器,後56位使用目前時間的毫秒表示進行随機。SessionTracker是Zookeeper服務端的會話管理器,負責會話的建立、管理和清理等工作。

6.4.3 會話管理

Zookeeper的會話管理主要是通過SessionTracker來負責,其采用了分桶政策(将類似的會話放在同一區塊中進行管理)進行管理,以便Zookeeper對會話進行不同區塊的隔離處理以及同一區塊的統一處理。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

Zookeeper将所有的會話都配置設定在不同的區塊一種,配置設定的原則是每個會話的下次逾時時間點(ExpirationTime)。ExpirationTime指該會話最近一次可能逾時的時間點。同時,Zookeeper Leader伺服器在運作過程中會定時地進行會話逾時檢查,時間間隔是ExpirationInterval,預設為tickTime的值,ExpirationTime的計算時間如下

ExpirationTime = ((CurrentTime + SessionTimeOut) / ExpirationInterval + 1) * ExpirationInterval

會了保持用戶端會話的有效性,用戶端會在會話逾時時間過期範圍内向服務端發送PING請求來保持會話的有效性(心跳檢測)。同時,服務端需要不斷地接收來自用戶端的心跳檢測,并且需要重新激活對應的用戶端會話,這個重新激活過程稱為TouchSession。會話激活不僅能夠使服務端檢測到對應用戶端的存貨性,同時也能讓用戶端自己保持連接配接狀态,其流程如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

如上圖所示,整個流程分為四步

1. 檢查該會話是否已經被關閉。若已經被關閉,則直接傳回即可。

2. 計算該會話新的逾時時間ExpirationTime_New。使用上面提到的公式計算下一次逾時時間點。

3. 擷取該會話上次逾時時間ExpirationTime_Old。計算該值是為了定位其所在的區塊。

3. 遷移會話。将該會話從老的區塊中取出,放入ExpirationTime_New對應的新區塊中。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

在上面會話激活過程中,隻要用戶端發送心跳檢測,服務端就會進行一次會話激活,心跳檢測由用戶端主動發起,以PING請求形式向服務端發送,在Zookeeper的實際設計中,隻要用戶端有請求發送到服務端,那麼就會觸發一次會話激活,以下兩種情況都會觸發會話激活。

1. 用戶端向服務端發送請求,包括讀寫請求,就會觸發會話激活。

2. 用戶端發現在sessionTimeout/3時間内尚未和服務端進行任何通信,那麼就會主動發起PING請求,服務端收到該請求後,就會觸發會話激活。

對于會話的逾時檢查而言,Zookeeper使用SessionTracker來負責,SessionTracker使用單獨的線程(逾時檢查線程)專門進行會話逾時檢查,即逐個一次地對會話桶中剩下的會話進行清理。如果一個會話被激活,那麼Zookeeper就會将其從上一個會話桶遷移到下一個會話桶中,如ExpirationTime 1 的session n 遷移到ExpirationTime n 中,此時ExpirationTime 1中留下的所有會話都是尚未被激活的,逾時檢查線程就定時檢查這個會話桶中所有剩下的未被遷移的會話,逾時檢查線程隻需要在這些指定時間點(ExpirationTime 1、ExpirationTime 2…)上進行檢查即可,這樣提高了檢查的效率,性能也非常好。

6.4.4 會話清理

當SessionTracker的會話逾時線程檢查出已經過期的會話後,就開始進行會話清理工作,大緻可以分為如下七步。

1. 标記會話狀态為已關閉。由于會話清理過程需要一段時間,為了保證在此期間不再處理來自該用戶端的請求,SessionTracker會首先将該會話的isClosing标記為true,這樣在會話清理期間接收到該用戶端的心情求也無法繼續處理了。

2. 發起會話關閉請求。為了使對該會話的關閉操作在整個服務端叢集都生效,Zookeeper使用了送出會話關閉請求的方式,并立即傳遞給PreRequestProcessor進行處理。

3. 收集需要清理的臨時節點。一旦某個會話失效後,那麼和該會話相關的臨時節點都需要被清理,是以,在清理之前,首先需要将伺服器上所有和該會話相關的臨時節點都整理出來。Zookeeper在記憶體資料庫中會為每個會話都單獨儲存了一份由該會話維護的所有臨時節點集合,在Zookeeper處理會話關閉請求之前,若正好有以下兩類請求到達了服務端并正在進行中。

· 節點删除請求,删除的目标節點正好是上述臨時節點中的一個。

· 臨時節點建立請求,建立的目标節點正好是上述臨時節點中的一個。

對于第一類請求,需要将所有請求對應的資料節點路徑從目前臨時節點清單中移出,以避免重複删除,對于第二類請求,需要将所有這些請求對應的資料節點路徑添加到目前臨時節點清單中,以删除這些即将被建立但是尚未儲存到記憶體資料庫中的臨時節點。

4. 添加節點删除事務變更。完成該會話相關的臨時節點收集後,Zookeeper會逐個将這些臨時節點轉換成"節點删除"請求,并放入事務變更隊列outstandingChanges中。

5. 删除臨時節點。FinalRequestProcessor會觸發記憶體資料庫,删除該會話對應的所有臨時節點。

6. 移除會話。完成節點删除後,需要将會話從SessionTracker中删除。

7. 關閉NIOServerCnxn。最後,從NIOServerCnxnFactory找到該會話對應的NIOServerCnxn,将其關閉。

6.4.5 重連

6.4.5.1 重連狀态(CONNECTED & EXPIRED)

當用戶端與服務端之間的網絡連接配接斷開時,Zookeeper用戶端會自動進行反複的重連,直到最終成功連接配接上Zookeeper叢集中的一台機器。此時,再次連接配接上服務端的用戶端有可能處于以下兩種狀态之一

1. CONNECTED。如果在會話逾時時間内重新連接配接上叢集中一台伺服器 。

2. EXPIRED。如果在會話逾時時間以外重新連接配接上,那麼服務端其實已經對該會話進行了會話清理操作,此時會話被視為非法會話。

在用戶端與服務端之間維持的是一個長連接配接,在sessionTimeout時間内,服務端會不斷地檢測該用戶端是否還處于正常連接配接,服務端會将用戶端的每次操作視為一次有效的心跳檢測來反複地進行會話激活。是以,在正常情況下,用戶端會話時一直有效的。然而,當用戶端與服務端之間的連接配接斷開後,使用者在用戶端可能主要看到兩類異常:CONNECTION_LOSS(連接配接斷開)和SESSION_EXPIRED(會話過期)。

6.4.5.2 重連異常: CONNECTION_LOSS(連接配接斷開)和SESSION_EXPIRED(會話過期)

連接配接斷開connection_loss:

有時因為網絡閃斷導緻用戶端與伺服器斷開連接配接,或是因為用戶端目前連接配接的伺服器出現問題導緻連接配接斷開,我麼稱“用戶端與伺服器斷開連接配接”現象,即connection_loss。在這種情況下,zookeeper用戶端會自動從位址清單中重新擷取新的位址并嘗試進行重新連接配接,直到最終成功連接配接上伺服器。
舉個例子:某應用在使用zookeeper用戶端進行setData操作時,正好出現了connection_loss現象,那麼用戶端會記錄接收到事件:none-disconnected通知,同時會抛出異常ConnectionLossException。這時,我們的應用需要做的事情是捕獲異常,然後等待zookeeper的用戶端自動完成重連,一旦用戶端成功連上一台zookeeper機器後,那麼用戶端就會收到事件none-syncconnnected通知,之後就可以重試剛才的setData操作。

會話失效session_expired

通常發生在connection_loss期間,用戶端和伺服器連接配接斷開後,由于重連期間耗時過長,超過了會話逾時時間限制後還沒有成功連接配接上伺服器,那麼伺服器認為這個會話已經結束了,就會開始進行會話清理,但是另一方面,該用戶端本身不知道會話已經失效了,并且其用戶端狀态還是disconnected。之後,如果用戶端重新連接配接上了伺服器,伺服器會告知用戶端會話已經失效,在這時,使用者就需要重新執行個體化一個zookeeper對象,并看應用的複雜程度,重新恢複臨時資料。

會話轉移session_moved

是指用戶端會話從一台伺服器轉移到另一台伺服器上,假設用戶端和伺服器s1之間的連接配接斷開後,如果通過嘗試重連後,成功連接配接上了新的伺服器s2并且延續了有效會話,那麼就可以說會話從s1轉移到了s2上。

6.5 伺服器啟動

服務端整體架構如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

Zookeeper伺服器的啟動,大緻可以分為以下五個步驟

1. 配置檔案解析。

2. 初始化資料管理器。

3. 初始化網絡I/O管理器。

4. 資料恢複。

5. 對外服務。

6.5.1 單機版伺服器啟動

單機版伺服器的啟動其流程圖如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

上圖的過程可以分為預啟動和初始化過程。

6.5.1.1 單機版伺服器啟動 - 預啟動

1. 統一由QuorumPeerMain作為啟動類。無論單機或叢集,在zkServer.cmd和zkServer.sh中都配置了QuorumPeerMain作為啟動入口類。

2. 解析配置檔案zoo.cfg。zoo.cfg配置運作時的基本參數,如tickTime、dataDir、clientPort等參數。

3. 建立并啟動曆史檔案清理器DatadirCleanupManager。對事務日志和快照資料檔案進行定時清理。

4. 判斷目前是叢集模式還是單機模式啟動。若是單機模式,則委托給ZooKeeperServerMain進行啟動。

5. 再次進行配置檔案zoo.cfg的解析。

6. 建立伺服器執行個體ZooKeeperServer。Zookeeper伺服器首先會進行伺服器執行個體的建立,然後對該伺服器執行個體進行初始化,包括連接配接器、記憶體資料庫、請求處理器等元件的初始化。

6.5.1.2 單機版伺服器啟動 - 初始化

1. 建立伺服器統計器ServerStats。ServerStats是Zookeeper伺服器運作時的統計器。

2. 建立Zookeeper資料管理器FileTxnSnapLog。FileTxnSnapLog是Zookeeper上層伺服器和底層資料存儲之間的對接層,提供了一系列操作資料檔案的接口,如事務日志檔案和快照資料檔案。Zookeeper根據zoo.cfg檔案中解析出的快照資料目錄dataDir和事務日志目錄dataLogDir來建立FileTxnSnapLog。

3. 設定伺服器tickTime和會話逾時時間限制。

4. 建立ServerCnxnFactory。通過配置系統屬性zookeper.serverCnxnFactory來指定使用Zookeeper自己實作的NIO還是使用Netty架構作為Zookeeper服務端網絡連接配接工廠。

5. 初始化ServerCnxnFactory。Zookeeper會初始化Thread作為ServerCnxnFactory的主線程,然後再初始化NIO伺服器。

6. 啟動ServerCnxnFactory主線程。進入Thread的run方法,此時服務端還不能處理用戶端請求。

7. 恢複本地資料。啟動時,需要從本地快照資料檔案和事務日志檔案進行資料恢複。

8. 建立并啟動會話管理器。Zookeeper會建立會話管理器SessionTracker進行會話管理。

9. 初始化Zookeeper的請求處理鍊。Zookeeper請求處理方式為責任鍊模式的實作。會有多個請求處理器依次處理一個用戶端請求,在伺服器啟動時,會将這些請求處理器串聯成一個請求處理鍊。

10. 注冊JMX服務。Zookeeper會将伺服器運作時的一些資訊以JMX的方式暴露給外部。

11. 注冊Zookeeper伺服器執行個體。将Zookeeper伺服器執行個體注冊給ServerCnxnFactory,之後Zookeeper就可以對外提供服務。

至此,單機版的Zookeeper伺服器啟動完畢。

6.5.2 叢集版伺服器啟動

單機和叢集伺服器的啟動在很多地方是一緻的,其流程圖如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

上圖的過程可以分為預啟動、初始化、Leader選舉、Leader與Follower啟動期互動過程、Leader與Follower啟動等過程。

6.5.2.1 叢集版伺服器啟動 - 預啟動

1. 統一由QuorumPeerMain作為啟動類。

2. 解析配置檔案zoo.cfg。

3. 建立并啟動曆史檔案清理器DatadirCleanupFactory。

4. 判斷目前是叢集模式還是單機模式的啟動。在叢集模式中,在zoo.cfg檔案中配置了多個伺服器位址,可以選擇叢集啟動。

6.5.2.2 叢集版伺服器啟動 - 初始化

1. 建立ServerCnxnFactory。

2. 初始化ServerCnxnFactory。

3. 建立Zookeeper資料管理器FileTxnSnapLog。

4. 建立QuorumPeer執行個體。Quorum是叢集模式下特有的對象,是Zookeeper伺服器執行個體(ZooKeeperServer)的托管者,QuorumPeer代表了叢集中的一台機器,在運作期間,QuorumPeer會不斷檢測目前伺服器執行個體的運作狀态,同時根據情況發起Leader選舉。

5. 建立記憶體資料庫ZKDatabase。ZKDatabase負責管理ZooKeeper的所有會話記錄以及DataTree和事務日志的存儲。

6. 初始化QuorumPeer。将核心元件如FileTxnSnapLog、ServerCnxnFactory、ZKDatabase注冊到QuorumPeer中,同時配置QuorumPeer的參數,如伺服器清單位址、Leader選舉算法和會話逾時時間限制等。

7. 恢複本地資料。

8. 啟動ServerCnxnFactory主線程。

6.6 Leader選舉

1. 初始化Leader選舉。叢集模式特有,Zookeeper首先會根據自身的伺服器ID(SID)、最新的ZXID(lastLoggedZxid)和目前的伺服器epoch(currentEpoch)來生成一個初始化投票,在初始化過程中,每個伺服器都會給自己投票。然後,根據zoo.cfg的配置,建立相應Leader選舉算法實作,Zookeeper提供了三種預設算法(LeaderElection、AuthFastLeaderElection、FastLeaderElection),可通過zoo.cfg中的electionAlg屬性來指定,但現隻支援FastLeaderElection選舉算法。在初始化階段,Zookeeper會建立Leader選舉所需的網絡I/O層QuorumCnxManager,同時啟動對Leader選舉端口的監聽,等待叢集中其他伺服器建立連接配接。

2. 注冊JMX服務。

3. 檢測目前伺服器狀态。運作期間,QuorumPeer會不斷檢測目前伺服器狀态。在正常情況下,Zookeeper伺服器的狀态在LOOKING、LEADING、FOLLOWING/OBSERVING之間進行切換。在啟動階段,QuorumPeer的初始狀态是LOOKING,是以開始進行Leader選舉。

4. Leader選舉。通過投票确定Leader,其餘機器稱為Follower和Observer。具體算法在後面會給出。

</font color=red>6.6.1 Leader選舉概述

6.6.1.1 伺服器啟動時期的Leader選舉

1、每個server會發出一個投票,由于是初始情況,是以對于server1和server2來說,都會将自己作為leader伺服器來投票,每次投票包含的最基本的元素為:所推舉的伺服器的myid和zxid,我們以(myid, zxid)的形式來表示。因為是初始化階段,是以無論是server1和是server2都會投給自己,即server1的投票為(1, 0),server2的投票為(2, 0),然後各自将這個投票發給叢集中其它所有機器。

2、接收來自各個伺服器的投票

每個伺服器都會接收來自其它伺服器的投票,接收到後會判斷該投票的有效性,包括檢查是否是本輪投票,是否來自looking狀态的伺服器。

3、處理投票

在接收到來自其它伺服器的投票後,針對每一個投票,伺服器都需要将别人的投票和自己的投票進行pk,pk的規則如下:優先檢查zxid,zxid大的伺服器優先作為leader。如果zxid相同,那麼比較myid,myid大的伺服器作為leader伺服器。

現在我們來看server1和server2實際是如何進行投票的,對于server1來說,他自己的投票是(1, 0),而接收到的投票為(2, 0)。首先會對比兩者的zxid,因為都是0,是以接下來會比較兩者的myid,server1發現接收到的投票中的myid是2,大于自己,于是就會更新自己的投票為(2, 0),然後重新将投票發出去,而對于server2,不需要更新自己的投票資訊,隻是再一次向叢集中的所有機器發出上一次投票資訊即可。

4、統計投票

每次投票後,伺服器都會統計所有投票,判斷是否已經有過半的機器接收到相同的投票資訊,對于server1和server2來說,都統計出叢集中已經

有兩台機器接受了(2, 0)這個投票資訊。這裡過半的概念是指大于叢集機器數量的一半,即大于或等于(n/2+1)。對于這裡由3台機器構成的叢集

大于等于2台即為達到過半要求。

5、改變伺服器狀态

一旦确定了leader,每個伺服器就會更新自己的狀态,如果是follower,那麼就變更為following,如果是leader,就變更為leading。

6.6.1.2 伺服器運作時期的Leader選舉

1、變更狀态

當leader挂了之後,餘下的非observer伺服器都會将自己的狀态變為looking,然後開始進行leader選舉流程。

2、每個server會發出一個投票

在這個過程中,需要生成投票資訊(myid, zxid),因為是運作期間,是以每個伺服器上的zxid可能不同,我們假定server1的zxid為123,而server3的zxid為122.在第一輪投票中,server1和server3都會投給自己,即分别産生投票(1, 123)和(3, 122),然後各自将這個投票發給叢集中的所有機器。

3、接收來自各個伺服器的投票

4、處理投票

對于投票的處理,和上面提到的伺服器啟動期間的處理規則是一緻的,在這個例子中,由于server1的zxid是123,server3的zxid是122,顯然server1會成為leader。

5、統計投票

6、改變伺服器狀态

6.6.2 Leader選舉的算法分析

6.6.2.1 術語解釋

SID: 在zoo.cfg檔案中,對叢集中的每一個server都賦予一個id,辨別着叢集中的一台server。每台機器不能重複,和myid值一緻

epoch:代表一個Leader周期。每當一個新的leader産生,該leader便會從伺服器本地日志中最大事務Proposal的zxid解析出epoch值,然後對其進行+1操作,作為新的epoch.

zxid:事務ID,辨別這對一次伺服器狀态的變更。是一個64bit的long數值,高32位辨別着目前epoch,低32位是計數器。Leader在産生一個新的事務Proposal的時候,都會對該計數器進行+1操作。

新的Leader産生的時候,epoch+1的同時,低32會被置為0,在此基礎上開始生成新的ZXID

Vote:投票

Quorum:過半機器數

6.6.2.2 進入leader選舉

當zookeeper叢集中的一台伺服器出現以下兩種情況之一時,就會開始進入leader選舉:

1、伺服器初始化啟動
2、伺服器運作期間無法和leader保持連接配接

而當一台機器進入leader選舉流程時,目前叢集也可能會處于以下兩種狀态:

1、叢集中本來就已經存在一個leader
2、叢集中确實不存在leader

我們首先來看第一種已經存在leader的情況,這種情況通常是叢集中的某一台機器啟動比較晚,在它啟動之前,叢集已經可以正常工作,即已經存在一台leader伺服器。針對這種情況,當該機器試圖去選舉leader的時候,會被告知目前伺服器的leader資訊,對于該機器來說,僅僅需要

和leader機器建立連接配接,并進行狀态同步即可。

6.6.3 Leader選舉的實作細節

1.LOOKING:尋找Leader狀态。處于該狀态的伺服器會認為目前叢集中不存在Leader,然後發起leader選舉。

2.FOLLOWING:表明目前伺服器角色是Follwer

3.LEADING:表明目前伺服器角色是Leader

4.OBSERVING:表明目前伺服器角色是Observer,不參與Leader選舉

6.7 各伺服器角色介紹

leader:

是整個叢集工作機制中的核心,其主要工作有:

1、事務請求的唯一排程和處理者,保證叢集事務處理的順序性。

2、叢集内部各伺服器的排程者。

follower:

是zookeeper叢集狀态的跟随者,其主要工作是:

1、處理用戶端的非事務請求,轉發事務請求給leader伺服器。

2、參與事務請求proposal的投票

3、參與leader選舉投票

observer

和follower唯一的差別在于,observer伺服器隻提供非事務服務,不參與任何形式的投票,包括事務請求proposal的投票和leader選舉投票。

通常在不影響叢集事務處理能力的前提下提升叢集的非事務處理能力。

6.7.1 Leader

Leader伺服器是Zookeeper叢集工作的核心,其主要工作如下

(1) 事務請求的唯一排程和處理者,保證叢集事務處理的順序性。

(2) 叢集内部各伺服器的排程者。

6.7.1.1 請求處理鍊

使用責任鍊來處理每個用戶端的請求時Zookeeper的特色,Leader伺服器的請求處理鍊如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

(1) PrepRequestProcessor。請求預處理器。在Zookeeper中,那些會改變伺服器狀态的請求稱為事務請求(建立節點、更新資料、删除節點、建立會話等),PrepRequestProcessor能夠識别出目前用戶端請求是否是事務請求。對于事務請求,PrepRequestProcessor處理器會對其進行一系列預處理,如建立請求事務頭、事務體、會話檢查、ACL檢查和版本檢查等。

(2) ProposalRequestProcessor。事務投票處理器。Leader伺服器事務處理流程的發起者,對于非事務性請求,ProposalRequestProcessor會直接将請求轉發到CommitProcessor處理器,不再做任何處理,而對于事務性請求,處理将請求轉發到CommitProcessor外,還會根據請求類型建立對應的Proposal提議,并發送給所有的Follower伺服器來發起一次叢集内的事務投票。同時,ProposalRequestProcessor還會将事務請求傳遞給SyncRequestProcessor進行事務日志的記錄。

(2) SyncRequestProcessor。事務日志記錄處理器。用來将事務請求記錄到事務日志檔案中,同時會觸發Zookeeper進行資料快照。

(3) AckRequestProcessor。負責在SyncRequestProcessor完成事務日志記錄後,向Proposal的投票收集器發送ACK回報,以通知投票收集器目前伺服器已經完成了對該Proposal的事務日志記錄。

(4) CommitProcessor。事務送出處理器。對于非事務請求,該處理器會直接将其傳遞給下一級處理器處理;對于事務請求,其會等待叢集内針對Proposal的投票直到該Proposal可被送出,利用CommitProcessor,每個伺服器都可以很好地控制對事務請求的順序處理。

(5) ToBeCommitProcessor。該處理器有一個toBeApplied隊列,用來存儲那些已經被CommitProcessor處理過的可被送出的Proposal。其會将這些請求傳遞給FinalRequestProcessor處理器處理,待其處理完後,再将其從toBeApplied隊列中移除。

(6) FinalRequestProcessor。用來進行用戶端請求傳回之前的操作,包括建立用戶端請求的響應,針對事務請求,該處理還會負責将事務應用到記憶體資料庫中去。

6.7.1.2 LearnerHandler

為了保證整個叢集内部的實時通信,同時為了確定可以控制所有的Follower/Observer伺服器,Leader伺服器會與每個Follower/Observer伺服器建立一個TCP長連接配接。同時也會為每個Follower/Observer伺服器建立一個名為LearnerHandler的實體。LearnerHandler是Learner伺服器的管理者,主要負責Follower/Observer伺服器和Leader伺服器之間的一系列網絡通信,包括資料同步、請求轉發和Proposal提議的投票等。Leader伺服器中儲存了所有Follower/Observer對應的LearnerHandler。

6.7.2 Follower

Follower是Zookeeper叢集的跟随者,其主要工作如下

(1) 處理用戶端非事務性請求(讀取資料),轉發事務請求給Leader伺服器。

(2) 參與事務請求Proposal的投票。

(3) 參與Leader選舉投票。

Follower也采用了責任鍊模式組裝的請求處理鍊來處理每一個用戶端請求,由于不需要對事務請求的投票處理,是以Follower的請求處理鍊會相對簡單,其處理鍊如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

(1) FollowerRequestProcessor。其用作識别目前請求是否是事務請求,若是,那麼Follower就會将該請求轉發給Leader伺服器,Leader伺服器是在接收到這個事務請求後,就會将其送出到請求處理鍊,按照正常事務請求進行處理。

(2) SendAckRequestProcessor。其承擔了事務日志記錄回報的角色,在完成事務日志記錄後,會向Leader伺服器發送ACK消息以表明自身完成了事務日志的記錄工作。

6.7.3 Observer

Observer充當觀察者角色,觀察Zookeeper叢集的最新狀态變化并将這些狀态同步過來,其對于非事務請求可以進行獨立處理,對于事務請求,則會轉發給Leader伺服器進行處理。Observer不會參與任何形式的投票,包括事務請求Proposal的投票和Leader選舉投票。其處理鍊如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

6.7.4 叢集間消息通信

Zookeeper的消息類型大體分為資料同步型、伺服器初始化型、請求處理型和會話管理型。

(1) 資料同步型。指在Learner和Leader伺服器進行資料同步時,網絡通信所用到的消息,通常有DIFF、TRUNC、SNAP、UPTODATE。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

(2) 伺服器初始化型。指在整個叢集或是某些新機器初始化時,Leader和Learner之間互相通信所使用的消息類型,常見的有OBSERVERINFO、FOLLOWERINFO、LEADERINFO、ACKEPOCH和NEWLEADER五種。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

(3) 請求處理型。指在進行清理時,Leader和Learner伺服器之間互相通信所使用的消息,常見的有REQUEST、PROPOSAL、ACK、COMMIT、INFORM和SYNC六種。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

(4) 會話管理型。指Zookeeper在進行會話管理時和Learner伺服器之間互相通信所使用的消息,常見的有PING和REVALIDATE兩種。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

6.8 請求處理

6.8.1 會話建立請求

Zookeeper服務端對于會話建立的處理,大體可以分為請求接收、會話建立、預處理、事務處理、事務應用和會話響應六大環節,其大體流程如

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

細分為以下23步

6.8.1.1 請求接收

(1) I/O層接收來自用戶端的請求。NIOServerCnxn維護每一個用戶端連接配接,用戶端與伺服器端的所有通信都是由NIOServerCnxn負責,其負責統一接收來自用戶端的所有請求,并将請求内容從底層網絡I/O中完整地讀取出來。

(2) 判斷是否是用戶端會話建立請求。每個會話對應一個NIOServerCnxn實體,對于每個請求,Zookeeper都會檢查目前NIOServerCnxn實體是否已經被初始化,如果尚未被初始化,那麼就可以确定該用戶端一定是會話建立請求。

(3) 反序列化ConnectRequest請求。一旦确定用戶端請求是否是會話建立請求,那麼服務端就可以對其進行反序列化,并生成一個ConnectRequest載體。

(4) 判斷是否是ReadOnly用戶端。如果目前Zookeeper伺服器是以ReadOnly模式啟動,那麼所有來自非ReadOnly型用戶端的請求将無法被處理。是以,服務端需要先檢查是否是ReadOnly用戶端,并以此來決定是否接受該會話建立請求。

(5) 檢查用戶端ZXID。正常情況下,在一個Zookeeper叢集中,服務端的ZXID必定大于用戶端的ZXID,是以若發現用戶端的ZXID大于服務端ZXID,那麼服務端不接受該用戶端的會話建立請求。

(6) 協商sessionTimeout。在用戶端向伺服器發送逾時時間後,伺服器會根據自己的逾時時間限制最終确定該會話逾時時間,這個過程就是sessionTimeout協商過程。

(7) 判斷是否需要重新激活建立會話。服務端根據用戶端請求中是否包含sessionID來判斷該用戶端是否需要重新建立會話,若客戶單請求中包含sessionID,那麼就認為該用戶端正在進行會話重連,這種情況下,服務端隻需要重新打開這個會話,否則需要重新建立。

6.8.1.2 會話建立

(8) 為用戶端生成sessionID。在為用戶端建立會話之前,服務端首先會為每個用戶端配置設定一個sessionID,服務端為用戶端配置設定的sessionID是全局唯一的。

(9) 注冊會話。向SessionTracker中注冊會話,SessionTracker中維護了sessionsWithTimeout和sessionsById,在會話建立初期,會将用戶端會話的相關資訊儲存到這兩個資料結構中。

(10) 激活會話。激活會話涉及Zookeeper會話管理的分桶政策,其核心是為會話安排一個區塊,以便會話清理程式能夠快速高效地進行會話清理。

(11) 生成會話密碼。服務端在建立一個用戶端會話時,會同時為用戶端生成一個會話密碼,連同sessionID一同發給用戶端,作為會話在叢集中不同機器間轉移的憑證。

6.8.1.3 預處理

(12) 将請求交給PrepRequestProcessor處理器處理。在送出給第一個請求處理器之前,Zookeeper會根據該請求所屬的會話,進行一次激活會話操作,以確定目前會話處于激活狀态,完成會話激活後,則送出請求至處理器。

(13) 建立請求事務頭。對于事務請求,Zookeeper會為其建立請求事務頭,服務端後續的請求處理器都是基于該請求頭來識别目前請求是否是事務請求,請求事務頭包含了一個事務請求最基本的一些資訊,包括sessionID、ZXID(事務請求對應的事務ZXID)、CXID(用戶端的操作序列)和請求類型(如create、delete、setData、createSession等)等。

(14) 建立請求事務體。由于此時是會話建立請求,其事務體是CreateSessionTxn。

(15) 注冊于激活會話。處理由非Leader伺服器轉發過來的會話建立請求。

6.8.1.4 事務處理

(16) 将請求交給ProposalRequestProcessor處理器。

  與提議相關的處理器,從ProposalRequestProcessor開始,請求的處理将會進入三個子處理流程,分别是Sync流程、Proposal流程、Commit流程。

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

Sync流程

使用SyncRequestProcessor處理器記錄事務日志,針對每個事務請求,都會通過事務日志的形式将其記錄,完成日志記錄後,每個Follower都會向Leader發送ACK消息,表明自身完成了事務日志的記錄,以便Leader統計每個事務請求的投票情況。

Proposal流程

每個事務請求都需要叢集中過半機器投票認可才能被真正應用到記憶體資料庫中,這個投票與統計過程就是Proposal流程。

· 發起投票。若目前請求是事務請求,Leader會發起一輪事務投票,在發起事務投票之前,會檢查目前服務端的ZXID是否可用。

· 生成提議Proposal。若ZXID可用,Zookeeper會将已建立的請求頭和事務體以及ZXID和請求本身序列化到Proposal對象中,此Proposal對象就是一個提議。

· 廣播提議。Leader以ZXID作為辨別,将該提議放入投票箱outstandingProposals中,同時将該提議廣播給所有Follower。

· 收集投票。Follower接收到Leader提議後,進入Sync流程進行日志記錄,記錄完成後,發送ACK消息至Leader伺服器,Leader根據這些ACK消息來統計每個提議的投票情況,當一個提議獲得半數以上投票時,就認為該提議通過,進入Commit階段。

· 将請求放入toBeApplied隊列中。

· 廣播Commit消息。Leader向Follower和Observer發送COMMIT消息。向Observer發送INFORM消息,向Leader發送ZXID。

Commit流程

· 将請求傳遞CommitProcessor。CommitProcessor收到請求後,将其放入queuedRequests隊列中。

· 處理queuedRequest隊列請求。CommitProcessor中單獨的線程處理queuedRequests隊列中的請求。

· 标記nextPending。若從queuedRequests中取出的是事務請求,則需要在叢集中進行投票處理,同時将nextPending标記位目前請求。

· 等待Proposal投票。在進行Commit流程的同時,Leader會生成Proposal并廣播給所有Follower伺服器,此時,Commit流程等待,直到投票結束。

· 投票通過。若提議獲得過半機器認可,則進入請求送出階段,該請求會被放入commitedRequests隊列中,同時喚醒Commit流程。

· 送出請求。若commitedRequests隊列中存在可以送出的請求,那麼Commit流程則開始送出請求,将請求放入toProcess隊列中,然後傳遞下一個請求處理器:FinalRequestProcessor。

6.8.1.5 事務應用

(17) 傳遞給FinalRequestProcessor處理器。FinalRequestProcessor處理器檢查outstandingChanges隊列中請求的有效性,若發現這些請求已經落後于目前正在處理的請求,那麼直接從outstandingChanges隊列中移除。

(18) 事務應用。之前的請求處理僅僅将事務請求記錄到了事務日志中,而記憶體資料庫中的狀态尚未改變,是以,需要将事務變更應用到記憶體資料庫。

(19) 将事務請求放入隊列commitProposal。完成事務應用後,則将該請求放入commitProposal隊列中,commitProposal用來儲存最近被送出的事務請求,以便叢集間機器進行資料的快速同步。

6.8.1.6 會話響應

(20) 統計處理。Zookeeper計算請求在服務端處理所花費的時間,統計用戶端連接配接的基本資訊,如lastZxid(最新的ZXID)、lastOp(最後一次和服務端的操作)、lastLatency(最後一次請求處理所花費的時間)等。

(21) 建立響應ConnectResponse。會話建立成功後的響應,包含了目前用戶端和服務端之間的通信協定版本号、會話逾時時間、sessionID和會話密碼。

(22) 序列化ConnectResponse。

(23) I/O層發送響應給用戶端。

6.8.2 SetData請求

6.8.3 事務請求轉發

6.8.4 GetData請求

6.9 資料與存儲

6.9.1 記憶體資料

Zookeeper的資料模型是樹結構,在記憶體資料庫中,存儲了整棵樹的内容,包括所有的節點路徑、節點資料、ACL資訊,Zookeeper會定時将這個資料存儲到磁盤上。

1. DataTree

DataTree是記憶體資料存儲的核心,是一個樹結構,代表了記憶體中一份完整的資料。DataTree不包含任何與網絡、用戶端連接配接及請求處理相關的業務邏輯,是一個獨立的元件。

2. DataNode

DataNode是資料存儲的最小單元,其内部除了儲存了結點的資料内容、ACL清單、節點狀态之外,還記錄了父節點的引用和子節點清單兩個屬性,其也提供了對子節點清單進行操作的接口。

3. ZKDatabase

Zookeeper的記憶體資料庫,管理Zookeeper的所有會話、DataTree存儲和事務日志。ZKDatabase會定時向磁盤dump快照資料,同時在Zookeeper啟動時,會通過磁盤的事務日志和快照檔案恢複成一個完整的記憶體資料庫。

6.9.2 事務日志

6.9.2.1 檔案存儲

在配置Zookeeper叢集時需要配置dataDir目錄,其用來存儲事務日志檔案。也可以為事務日志單獨配置設定一個檔案存儲目錄:dataLogDir。若配置dataLogDir為/home/admin/zkData/zk_log,那麼Zookeeper在運作過程中會在該目錄下建立一個名字為version-2的子目錄,該目錄确定了目前Zookeeper使用的事務日志格式版本号,當下次某個Zookeeper版本對事務日志格式進行變更時,此目錄也會變更,即在version-2子目錄下會生成一系列檔案大小一緻(64MB)的檔案。

6.9.2.2 日志格式

在配置好日志檔案目錄,啟動Zookeeper後,完成如下操作

(1) 建立/test_log節點,初始值為v1。

(2) 更新/test_log節點的資料為v2。

(3) 建立/test_log/c節點,初始值為v1。

(4) 删除/test_log/c節點。

經過四步操作後,會在/log/version-2/目錄下生成一個日志檔案,筆者下是log.cec。

将Zookeeper下的zookeeper-3.4.6.jar和slf4j-api-1.6.1.jar複制到/log/version-2目錄下,使用如下指令打開log.cec檔案。

java -classpath ./zookeeper-3.4.6.jar:./slf4j-api-1.6.1.jar org.apache.zookeeper.server.LogFormatter log.cec

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 。是檔案頭資訊,主要是事務日志的DBID和日志格式版本号。

…session 0x159…0xcec createSession 30000。表示用戶端會話建立操作。

…session 0x159…0xced create '/test_log,… 。表示建立/test_log節點,資料内容為#7631(v1)。

…session 0x159…0xcee setData ‘/test_log,…。表示設定了/test_log節點資料,内容為#7632(v2)。

…session 0x159…0xcef create ’/test_log/c,…。表示建立節點/test_log/c。

…session 0x159…0xcf0 delete '/test_log/c。表示删除節點/test_log/c。

6.9.2.3 日志寫入

FileTxnLog負責維護事務日志對外的接口,包括事務日志的寫入和讀取等。Zookeeper的事務日志寫入過程大體可以分為如下6個步驟。

(1) 确定是否有事務日志可寫。當Zookeeper伺服器啟動完成需要進行第一次事務日志的寫入,或是上一次事務日志寫滿時,都會處于與事務日志檔案斷開的狀态,即Zookeeper伺服器沒有和任意一個日志檔案相關聯。是以在進行事務日志寫入前,Zookeeper首先會判斷FileTxnLog元件是否已經關聯上一個可寫的事務日志檔案。若沒有,則會使用該事務操作關聯的ZXID作為字尾建立一個事務日志檔案,同時建構事務日志的檔案頭資訊,并立即寫入這個事務日志檔案中去,同時将該檔案的檔案流放入streamToFlush集合,該集合用來記錄目前需要強制進行資料落盤的檔案流。

(2) 确定事務日志檔案是否需要擴容(預配置設定)。Zookeeper會采用磁盤空間預配置設定政策。當檢測到目前事務日志檔案剩餘空間不足4096位元組時,就會開始進行檔案空間擴容,即在現有檔案大小上,将檔案增加65536KB(64MB),然後使用"0"填充被擴容的檔案空間。

(3) 事務序列化。對事務頭和事務體的序列化,其中事務體又可分為會話建立事務、節點建立事務、節點删除事務、節點資料更新事務等。

(4) 生成Checksum。為保證日志檔案的完整性和資料的準确性,Zookeeper在将事務日志寫入檔案前,會計算生成Checksum。

(5) 寫入事務日志檔案流。将序列化後的事務頭、事務體和Checksum寫入檔案流中,此時并為寫入到磁盤上。

(6) 事務日志刷入磁盤。由于步驟5中的緩存原因,無法實時地寫入磁盤檔案中,是以需要将緩存資料強制刷入磁盤。

6.9.2.4 日志截斷

在Zookeeper運作過程中,可能出現非Leader記錄的事務ID比Leader上大,這是非法運作狀态。此時,需要保證所有機器必須與該Leader的資料保持同步,即Leader會發送TRUNC指令給該機器,要求進行日志截斷,Learner收到該指令後,就會删除所有包含或大于該事務ID的事務日志檔案。

6.9.3 snapshot——資料快照

資料快照是Zookeeper資料存儲中非常核心的運作機制,資料快照用來記錄Zookeeper伺服器上某一時刻的全量記憶體資料内容,并将其寫入指定的磁盤檔案中。

6.9.3.1 檔案存儲

與事務檔案類似,Zookeeper快照檔案也可以指定特定磁盤目錄,通過dataDir屬性來配置。若指定dataDir為/home/admin/zkData/zk_data,則在運作過程中會在該目錄下建立version-2的目錄,該目錄确定了目前Zookeeper使用的快照資料格式版本号。在Zookeeper運作時,會生成一系列檔案。

6.9.3.2 資料快照

FileSnap負責維護快照資料對外的接口,包括快照資料的寫入和讀取等,将記憶體資料庫寫入快照資料檔案其實是一個序列化過程。針對用戶端的每一次事務操作,Zookeeper都會将他們記錄到事務日志中,同時也會将資料變更應用到記憶體資料庫中,Zookeeper在進行若幹次事務日志記錄後,将記憶體資料庫的全量資料Dump到本地檔案中,這就是資料快照。其步驟如下

(1) 确定是否需要進行資料快照。每進行一次事務日志記錄之後,Zookeeper都會檢測目前是否需要進行資料快照,考慮到資料快照對于Zookeeper機器的影響,需要盡量避免Zookeeper叢集中的所有機器在同一時刻進行資料快照。采用過半随機政策進行資料快照操作。

(2) 切換事務日志檔案。表示目前的事務日志已經寫滿,需要重新建立一個新的事務日志。

(3) 建立資料快照異步線程。建立單獨的異步線程來進行資料快照以避免影響Zookeeper主流程。

(4) 擷取全量資料和會話資訊。從ZKDatabase中擷取到DataTree和會話資訊。

(5) 生成快照資料檔案名。Zookeeper根據目前已經送出的最大ZXID來生成資料快照檔案名。

(6) 資料序列化。首先序列化檔案頭資訊,然後再對會話資訊和DataTree分别進行序列化,同時生成一個Checksum,一并寫入快照資料檔案中去。

6.9.4 初始化

在Zookeeper伺服器啟動期間,首先會進行資料初始化工作,用于将存儲在磁盤上的資料檔案加載到Zookeeper伺服器記憶體中。

6.9.4.1 初始化流程

Zookeeper的初始化過程如下圖所示

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

資料的初始化工作是從磁盤上加載資料的過程,主要包括了從快照檔案中加載快照資料和根據實物日志進行資料修正兩個過程。

(1) 初始化FileTxnSnapLog。FileTxnSnapLog是Zookeeper事務日志和快照資料通路層,用于銜接上層業務和底層資料存儲,底層資料包含了事務日志和快照資料兩部分。FileTxnSnapLog中對應FileTxnLog和FileSnap。

(2) 初始化ZKDatabase。首先建構DataTree,同時将FileTxnSnapLog傳遞ZKDatabase,以便記憶體資料庫能夠對事務日志和快照資料進行通路。在ZKDatabase初始化時,DataTree也會進行相應的初始化工作,如建立一些預設結點,如/、/zookeeper、/zookeeper/quota三個節點。

(3) 建立PlayBackListener。其主要用來接收事務應用過程中的回調,在Zookeeper資料恢複後期,會有事務修正過程,此過程會回調PlayBackListener來進行對應的資料修正。

(4) 處理快照檔案。此時可以從磁盤中恢複資料了,首先從快照檔案開始加載。

(5) 擷取最新的100個快照檔案。更新時間最晚的快照檔案包含了最新的全量資料。

(6) 解析快照檔案。逐個解析快照檔案,此時需要進行反序列化,生成DataTree和sessionsWithTimeouts,同時還會校驗Checksum及快照檔案的正确性。對于100個快找檔案,如果正确性校驗通過時,通常隻會解析最新的那個快照檔案。隻有最新快照檔案不可用時,才會逐個進行解析,直至100個快照檔案全部解析完。若将100個快照檔案解析完後還是無法成功恢複一個完整的DataTree和sessionWithTimeouts,此時伺服器啟動失敗。

(7) 擷取最新的ZXID。此時根據快照檔案的檔案名即可解析出最新的ZXID:zxid_for_snap。該ZXID代表了Zookeeper開始進行資料快照的時刻。

(8) 處理事務日志。此時伺服器記憶體中已經有了一份近似全量的資料,現在開始通過事務日志來更新增量資料。

(9) 擷取所有zxid_for_snap之後送出的事務。此時,已經可以擷取快照資料的最新ZXID。隻需要從事務日志中擷取所有ZXID比步驟7得到的ZXID大的事務操作。

(10) 事務應用。擷取大于zxid_for_snap的事務後,将其逐個應用到之前基于快照資料檔案恢複出來的DataTree和sessionsWithTimeouts。每當有一個事務被應用到記憶體資料庫中後,Zookeeper同時會回調PlayBackListener,将這事務操作記錄轉換成Proposal,并儲存到ZKDatabase的committedLog中,以便Follower進行快速同步。

(11) 擷取最新的ZXID。待所有的事務都被完整地應用到記憶體資料庫中後,也就基本上完成了資料的初始化過程,此時再次擷取ZXID,用來辨別上次伺服器正常運作時送出的最大事務ID。

(12) 校驗epoch。epoch辨別了目前Leader周期,叢集機器互相通信時,會帶上這個epoch以確定彼此在同一個Leader周期中。完成資料加載後,Zookeeper會從步驟11中确定ZXID中解析出事務處理的Leader周期:epochOfZxid。同時也會從磁盤的currentEpoch和acceptedEpoch檔案中讀取上次記錄的最新的epoch值,進行校驗。

6.9.5 資料同步

整個叢集完成Leader選舉後,Learner會向Leader進行注冊,當Learner向Leader完成注冊後,就進入資料同步環節,同步過程就是Leader将那些沒有在Learner伺服器上送出過的事務請求同步給Learner伺服器,大體過程如下

《從Paxos到zookeeper分布式一緻性原理與實踐》筆記《從Paxos到zookeeper分布式一緻性原理與實踐》筆記一、概念二、一緻性協調三、Zookeeper四、使用Zookeeper五、Zookeeper應用場景《從Paxos到zookeeper分布式一緻性原理與實踐》筆記第6章ZooKeeper技術内幕七、Zookeeper運維reference

(1) 擷取Learner狀态。在注冊Learner的最後階段,Learner伺服器會發送給Leader伺服器一個ACKEPOCH資料包,Leader會從這個資料包中解析出該Learner的currentEpoch和lastZxid。

(2) 資料同步初始化。首先從Zookeeper記憶體資料庫中提取出事務請求對應的提議緩存隊列proposals,同時完成peerLastZxid(該Learner最後處理的ZXID)、minCommittedLog(Leader提議緩存隊列commitedLog中最小的ZXID)、maxCommittedLog(Leader提議緩存隊列commitedLog中的最大ZXID)三個ZXID值的初始化。

對于叢集資料同步而言,通常分為四類,直接差異化同步(DIFF同步)、先復原再差異化同步(TRUNC+DIFF同步)、僅復原同步(TRUNC同步)、全量同步(SNAP同步),在初始化階段,Leader會優先以全量同步方式來同步資料。同時,會根據Leader和Learner之間的資料差異情況來決定最終的資料同步方式。

· 直接差異化同步(DIFF同步,peerLastZxid介于minCommittedLog和maxCommittedLog之間)。Leader首先向這個Learner發送一個DIFF指令,用于通知Learner進入差異化資料同步階段,Leader即将把一些Proposal同步給自己,針對每個Proposal,Leader都會通過發送PROPOSAL内容資料包和COMMIT指令資料包來完成,

· 先復原再差異化同步(TRUNC+DIFF同步,Leader已經将事務記錄到本地事務日志中,但是沒有成功發起Proposal流程)。當Leader發現某個Learner包含了一條自己沒有的事務記錄,那麼就需要該Learner進行事務復原,復原到Leader伺服器上存在的,同時也是最接近于peerLastZxid的ZXID。

· 僅復原同步(TRUNC同步,peerLastZxid大于maxCommittedLog)。Leader要求Learner復原到ZXID值為maxCommittedLog對應的事務操作。

· 全量同步(SNAP同步,peerLastZxid小于minCommittedLog或peerLastZxid不等于lastProcessedZxid)。Leader無法直接使用提議緩存隊列和Learner進行同步,是以隻能進行全量同步。Leader将本機的全量記憶體資料同步給Learner。Leader首先向Learner發送一個SNAP指令,通知Learner即将進行全量同步,随後,Leader會從記憶體資料庫中擷取到全量的資料節點和會話逾時時間記錄器,将他們序列化後傳輸給Learner。Learner接收到該全量資料後,會對其反序列化後載入到記憶體資料庫中。

七、Zookeeper運維

7.1 配置參數

指令 解釋
dataDir 用于配置走開伺服器的快照檔案目錄,預設情況下,如果沒有配置dataLogDir,那麼事務日志也會存儲在這個目錄中。考慮到事務日志的寫性能直接影響zookeeper整體的服務能力,是以建議同時設定dataDir和dataLogDir。
dataLogDir 存儲事務日志檔案,zookeeper在傳回用戶端事務請求響應之前,必須将本次請求對應的事務日志寫入到磁盤中,是以,事務日志寫入的性能直接确定了zookeeper在處理事務請求時的吞吐。尤其是上文中提到的資料快照操作,會極大的影響事務日志的寫性能,是以盡量給事務日志的輸出配置一個單獨的磁盤或是挂載點,将極大的提升zookeeper的整體性能。
initLimit 預設為10,用于配置leader伺服器等待follower啟動,并完成資料同步的時間,follower伺服器再啟動過程中,會與leader建立連接配接并完成資料同步,進而确定自己對外提供服務的起始狀态。leader伺服器允許follower在initLimit時間内完成這個工作。通常情況下,不用修改這個參數,但随着zookeeper叢集管理的資料量的增大,follower伺服器在啟動的時候,從leader上進行同步資料的時間也會相應邊長,于是無法在較短的時間完成資料同步,是以,在這種情況下,需要調大這個參數。
syncLimit 預設值5,用于配置leader伺服器和follower之間進行心跳檢測的最大延時時間,在zookeeper叢集運作過程中,leader伺服器會與所有的follower進行心跳檢測來确定該伺服器是否存活,如果leader伺服器在syncLimit時間内無法擷取到follower的心跳檢測響應,那麼leader就會認為該follower已經脫離了和自己的同步。一般使用預設值即可,除非網絡環境較差。
snapCount 預設100000,用于配置相鄰兩次資料快照之間的事務操作次數,即zookeeper會在snapCount次事務操作後進行一次資料快照。
preAllocSize 預設值是65535,即64MB。用于設定事務日志檔案的預配置設定磁盤空間,如果我們修改了snapCount的值,那麼preAllocSize參數也要随着做出變更。

minSessionTimeout

/maxSessionTimeout

分别預設值是2倍和20倍,這兩個參數用于服務端對用戶端會話的逾時時間進行限制,如果用戶端設定的逾時時間不在該範圍内,那麼會被伺服器強制設定為最大或最小逾時時間。
jute.maxbuffer 預設值1048575,機關位元組,用于配置單個資料節點znode上可以存儲的最大資料量大小,通常需要考慮到zookeeper上不适宜存儲太多的資料,往往需要将該參數設定的更小,在變更該參數時,需要在zookeeper叢集的所有機器以及所有用戶端上設定才能生效。
server.id=host:port:port 配置zookeeper叢集機器清單,其中id為serverID,與每台伺服器myid檔案中的數字對應,同時,在該參數中,會配置兩個端口。第一個用于指定follower伺服器與leader進行運作時通信和資料同步時所使用的端口,第二個則專門用于leader選舉過程中的投票通信。
autopurge.snapRetainCount 預設值為3,zookeeper增加了對曆史事務日志和快照資料自動清理的功能,該參數用于配置zookeeper在自動清理時需要保留的快照資料檔案數量和對應的事務日志檔案。并不是磁盤上的所有檔案都可以被清理,這樣将無法恢複資料。是以該參數的最小值是3,如果配置的比3小,則會被自動調整到3。
autopurge.purgeInterval 預設值為0,用于配置zookeeper進行曆史檔案自動清理的頻率,該值為0表示不需要開啟定時清理功能。
fsync.warningthresholdms 預設1000毫秒,用于配置zookeeper進行事務日志fsync操作時消耗時間的報警門檻值,一旦進行一個fsync操作消耗的時間大于該參數,就在日志中列印出報警日志。
forceSync 預設值為yes,用于配置zookeeper是否在事務送出的時候,将日志寫入操作強制重新整理磁盤,預設是yes,即每次事務日志寫入操作都會實時刷入磁盤,如果是no,可以提高zookeeper寫性能,但存在類似機器斷電這樣的安全風險。
globalOutstandingLimit 預設1000,配置zookeeper伺服器最大請求堆積量,在zookeeper運作過程中,用戶端會不斷的将請求發送到服務端,為了防止服務端資源耗盡,服務端必須限制同時處理的請求數,即最大請求堆積數量。
leaderServes 預設為yes,配置leader是否有可以接受用戶端連接配接,即是否允許leader向用戶端提供服務,預設情況下,leader伺服器能夠接受并處理用戶端讀寫請求,在zookeeper的設計中,leader伺服器主要用于進行對事務更新請求的協調以及叢集本身的運作時協調,是以,可以設定讓leader伺服器不接受用戶端的連接配接,使其專注于進行分布式協調。
skipAcl 預設為no,配置是否可以跳過acl權限檢查,預設情況下,會對每一個用戶端請求進行權限檢查,如果設定為yes,則能一定程度的提升zookeeper的讀寫性能,但同時也将向所有用戶端開放zookeeper的資料,包括那些之前設定過acl權限的資料節點,也将不再接受權限控制。
cnxTimeout 預設5000毫秒,配置在leader選舉過程中,各伺服器之間進行tcp連接配接建立的逾時時間。

7.1 2四字指令

先Telnet上伺服器:telnet localhost 2181

指令 解釋
conf 輸出zookeeper伺服器運作時使用的基本配置資訊,包括clientPort、dataDir、tickTime等。
cons 輸出目前這台伺服器上所有用戶端連接配接的詳細資訊,包括每個用戶端的用戶端ip、會話id和最後一次與伺服器互動的操作類型等。
crst 是一個功能性指令,用于重置所有的用戶端連接配接統計資訊。
dump 用于輸出目前叢集的所有會話資訊,包括這些會話的會話id,以及每個會話建立的臨時節點等資訊,另外,隻有leader伺服器會進行所有會話的逾時檢測,是以,如果在leader上執行該指令,還能夠看到每個會話的逾時時間。
envi 輸出zookeeper所在伺服器運作時的環境資訊。
ruok 輸出目前zookeeper伺服器是否正在運作,該指令的名字非常有趣,諧音正好是are you ok。執行該指令後,如果目前zookeeper伺服器正在運作,那麼傳回imok,否則沒有任何輸出。這個指令隻能說明2181端口開着,想要更可靠的擷取更多zookeeper運作狀态資訊,可以使用stat指令。
stat 用于擷取zookeeper伺服器的運作時狀态資訊,包括基本的zookeeper版本、打包資訊、運作時角色、叢集資料節點個數等資訊,還會将目前伺服器的用戶端連接配接列印出來。還會輸出一些伺服器的統計資訊,包括延遲情況,收到請求數和傳回的響應數等。
srst 是一個功能指令,用于重置所有伺服器的統計資訊。
wchs 指令用于輸出目前伺服器上管理的watcher的概要資訊。
wchc 用于輸出目前伺服器上管理的watcher的詳細資訊,以會話為機關進行歸組,同時列出被該會話注冊了watcher的節點路徑。
wchp 和wahc一樣,不同點在于該指令的輸出資訊以節點路徑為機關進行歸組。
mntr 用于輸出比stat指令更詳盡的伺服器統計資訊,包括請求處理的延遲情況、伺服器記憶體資料庫大小和叢集的資料同步情況。

reference

【分布式】Zookeeper與Paxos

https://github.com/yhb2010/zookeeper-paxos

繼續閱讀