zookeeper原理與應用

zookeeper(簡稱zk),顧名思義,爲動物園管理員的意思,動物對應服務節點,zk是這些節點的管理者。
在分佈式場景中,zk的應用很是普遍。 zk的一致性,是指數據在多個副本之間保持一致的特性
CAP定理告訴咱們,在分佈式系統設計中,P(分區容錯性)是不可缺乏的,所以只能在A(可用性)與C(一致性)間作取捨。 zookeeper是CPhtml

基本概念:

  • 數據節點(dataNode):zk數據模型中的最小數據單元,數據模型是一棵樹,由斜槓(/)分割的路徑名惟一標識,數據節點能夠存儲數據內容及一系列屬性信息,同時還能夠掛載子節點,構成一個層次化的命名空間。java

  • 會話(Session):指zk客戶端與zk服務器之間的會話,在zk中,會話是經過客戶端和服務器之間的一個TCP長鏈接來實現的。經過這個長鏈接,客戶端可以使用心跳檢測與服務器保持有效的會話,也能向服務器發送請求並接收響應,還可接收服務器的Watcher事件通知。Session的sessionTimeout,是會話超時時間,若是這段時間內,客戶端未與服務器發生任何溝通(心跳或請求),服務器端會清除該session數據,客戶端的TCP長鏈接將不可用,這種狀況下,客戶端須要從新實例化一個Zookeeper對象。node

  • 事務及ZXID:事務是指可以改變Zookeeper服務器狀態的操做,通常包括數據節點的建立與刪除、數據節點內容更新和客戶端會話建立與失效等操做。對於每一個事務請求,zk都會爲其分配一個全局惟一的事務ID,即ZXID,是一個64位的數字,高32位表示該事務發生的集羣選舉週期(集羣每發生一次leader選舉,值加1),低32位表示該事務在當前選擇週期內的遞增次序(leader每處理一個事務請求,值加1,發生一次leader選擇,低32位要清0)。 事務日誌:全部事務操做都是須要記錄到日誌文件中的,可經過 dataLogDir配置文件目錄,文件是以寫入的第一條事務zxid爲後綴,方便後續的定位查找。zk會採起「磁盤空間預分配」的策略,來避免磁盤Seek頻率,提高zk服務器對事務請求的影響能力。默認設置下,每次事務日誌寫入操做都會實時刷入磁盤,也能夠設置成非實時(寫到內存文件流,定時批量寫入磁盤),但那樣斷電時會帶來丟失數據的風險。算法

  • 數據快照:數據快照是zk數據存儲中另外一個很是核心的運行機制。數據快照用來記錄zk服務器上某一時刻的全量內存數據內容,並將其寫入到指定的磁盤文件中,可經過dataDir配置文件目錄。可配置參數snapCount,設置兩次快照之間的事務操做個數,zk節點記錄完事務日誌時,會統計判斷是否須要作數據快照(距離上次快照,事務操做次數等於snapCount/2~snapCount 中的某個值時,會觸發快照生成操做,隨機值是爲了不全部節點同時生成快照,致使集羣影響緩慢)。數據庫

  • 過半:所謂「過半」是指大於集羣機器數量的一半,即大於或等於(n/2+1),此處的「集羣機器數量」不包括observer角色節點。leader廣播一個事務消息後,當收到半數以上的ack信息時,就認爲集羣中全部節點都收到了消息,而後leader就不須要再等待剩餘節點的ack,直接廣播commit消息,提交事務。選舉中的投票提議及數據同步時,也是如此,leader不須要等到全部learner節點的反饋,只要收到過半的反饋就可進行下一步操做。apache

Zookeeper的角色及架構

zk集羣由多個節點組成,其中有且僅有一個leader,處理全部事務請求;follower及observer統稱learner。learner須要同步leader的數據。follower還參與選舉及事務決策過程。zk客戶端會打散配置文件中的serverAddress 順序並隨機組成新的list,而後循環按序取一個服務器地址進行鏈接,直到成功。follower及observer會將事務請求轉交給leader處理。api

Zookeeper的一致性協議

ZAB(ZooKeeper Atomic Broadcast)是爲ZooKeeper設計的一種支持崩潰恢復的原子廣播協議。bash

包括崩潰恢復(選主+數據同步)和消息廣播(事務操做)。服務器

當leader崩潰或者leader失去大多數的follower 進入恢復模式session

崩潰恢復  

半數經過 – 3臺機器 掛一臺 2>3/2– 4臺機器 掛2臺 2!>4/2

(1)設置狀態爲LOOKING,初始化內部投票Vote (id,zxid) 數據至內存,並將其廣播到集羣其它節點。節點首次投票都是選舉本身做爲leader,將自身的服務ID、處理的最近一個事務請求的ZXID(ZXID是從內存數據庫裏取的,即該節點最近一個完成commit的事務id)及當前狀態廣播出去。而後進入循環等待及處理其它節點的投票信息的流程中。
(2)循環等待流程中,節點每收到一個外部的Vote信息,都須要將其與本身內存Vote數據進行PK,規則爲取ZXID大的,若ZXID相等,則取ID大的那個投票。若外部投票勝選,節點須要將該選票覆蓋以前的內存Vote數據,並再次廣播出去;同時還要統計是否有過半的贊同者與新的內存投票數據一致,無則繼續循環等待新的投票,有則須要判斷leader是否在贊同者之中,在則退出循環,選舉結束,根據選舉結果及各自角色切換狀態,leader切換成LEADING、follower切換到FOLLOWING、observer切換到OBSERVING狀態

廣播(同步)

一旦leader已經和多數的follower進行了狀態同步後,他就能夠開始廣播消息了,即進入廣播狀態。
這時候當一個server加入zookeeper服務中,它會在恢復模式下啓動,發現leader,並和leader進行狀態同步。待到同步結束,它也參與消息廣播。
Zookeeper服務一直維持在Broadcast狀態,直到leader崩潰了或者leader失去了大部分的followers支持

很是棒的zk工做流程介紹

ZooKeeper提供了什麼?

  • 文件系統
  • 通知機制

Zookeeper能夠用來作什麼(應用)

zk經常使用於命名服務、配置管理、集羣管理、分佈式鎖、隊列管理

命名服務

名字服務
這個主要是做爲分佈式命名服務,利用zookeeper的文件系統,經過調用zk的create node api,可以很容易建立一個全局惟一的path,這個path就能夠做爲一個名稱。

命名服務是指經過指定的名字來獲取資源或者服務的地址,提供者的信息。利用Zookeeper很容易建立一個全局的路徑,而這個路徑就能夠做爲一個名字,它能夠指向集羣中的集羣,提供的服務的地址,遠程對象等。簡單來講使用Zookeeper作命名服務就是用路徑做爲名字,路徑上的數據就是其名字指向的實體。
服務提供者在啓動的時候,向ZK上的指定節點(如/{serviceName}/providers)目錄下寫入本身的URL地址,這個操做就完成了服務的發佈。
服務消費者啓動的時候,訂閱/{serviceName}/providers/目錄下的提供者URL地址(若是一次性使用直接讀取便可),並向/{serviceName}/consumers/目錄下寫入本身的URL地址(若是服務提供者者須要經過ZK獲取服務消費者身份,可選)。
全部向ZK上註冊的地址都是臨時節點,這樣就可以保證服務提供者和消費者可以自動感應資源的變化。
如dubbo作的就是上面這部分操做

nameservice:

  • -m 程序運行的方式,指定是服務提供者provider仍是服務消費者consumer
  • -n 服務名稱
  • -s Zookeeper的服務地址IP:PORT
服務提供者:  
  nameservice -m provider -n ServiceDemo -s 172.17.0.36:2181  
服務消費者:
  nameservice -m consumer -n ServiceDemo -s 172.17.0.36:2181
服務監控者:
  nameservice -m monitor -n ServiceDemo -s 172.17.0.36:2181
複製代碼

第一條命令啓動服務提供者,它提供一個ServiceDemo的服務,首次啓動後會建立/NameService/、/NameService/ServiceDemo/、/NameService/ServiceDemo/provider/幾個路徑(永久節點)。而後在服務提供進程在/NameService/ServiceDemo/provider/下建立臨時序列節點。

第二條命令啓動一個服務消費進程,它在/NameService/ServiceDemo/consumer/下建立臨時序列節點,並watch /NameService/ServiceDemo/provider/下的子節點變化事件,及時更新provider列表。

第三條命令是啓動一個服務監控進程,它watch /NameService/ServiceDemo/provider,/NameService/ServiceDemo/consumer/兩個路徑的子節點變化,及時更新provider列表和comsumer列表。

配置管理

經過zookeeper的監聽功能 實現配置管理 common.yml改了,也不須要系統A、B、C重啓。

系統A、B、C都使用着這份配置 作法:咱們能夠將common.yml這份配置放在ZooKeeper的Znode節點中,系統A、B、C監聽着這個Znode節點有無變動,若是變動了,及時響應。

系統A、B、C監聽着ZooKeeper的節點,一旦common.yml內容有變化,及時響應

參考 : 基於zookeeper實現統一配置管理

集羣管理

利用ZooKeeper的兩大特性,就能夠實現另外一種集羣機器存活性監控的系統。例如,監控系統在/clusterServers節點上註冊一個Watcher監聽,那麼但凡進行動態添加機器的操做,就會在/clusterServers節點下建立一個臨時節點:/clusterServer/[Hostname]。這樣一來,監控系統就可以實時檢測到機器的變更狀況,至於後續處理就是監控系統的業務了。

分佈式鎖

zookeeper實現分佈式鎖的算法流程,假設鎖空間的根節點爲/lock:

  1. 客戶端鏈接zookeeper,並在/lock下建立臨時的且有序的子節點,第一個客戶端對應的子節點爲/lock/lock-0000000000,第二個爲/lock/lock-0000000001,以此類推。
  2. 客戶端獲取/lock下的子節點列表,判斷本身建立的子節點是否爲當前子節點列表中序號最小的子節點,若是是則認爲得到鎖,不然監聽/lock的子節點變動消息,得到子節點變動通知後重復此步驟直至得到鎖;
  3. 執行業務代碼;
  4. 完成業務流程後,刪除對應的子節點釋放鎖。

這個算法有個極大的優化點:假如當前有1000個節點在等待鎖,若是得到鎖的客戶端釋放鎖時,這1000個客戶端都會被喚醒,這種狀況稱爲「羊羣效應」;在這種羊羣效應中,zookeeper須要通知1000個客戶端,這會阻塞其餘的操做,最好的狀況應該只喚醒新的最小節點對應的客戶端。應該怎麼作呢?在設置事件監聽時,每一個客戶端應該對恰好在它以前的子節點設置事件監聽,例如子節點列表爲/lock/lock-0000000000、/lock/lock-000000000一、/lock/lock-0000000002,序號爲1的客戶端監聽序號爲0的子節點刪除消息,序號爲2的監聽序號爲1的子節點刪除消息。 因此調整後的分佈式鎖算法流程以下:

  1. 客戶端鏈接zookeeper,並在/lock下建立臨時的且有序的子節點,第一個客戶端對應的子節點爲/lock/lock-0000000000,第二個爲/lock/lock-0000000001,以此類推。
  2. 客戶端獲取/lock下的子節點列表,判斷本身建立的子節點是否爲當前子節點列表中序號最小的子節點,若是是則認爲得到鎖,不然監聽恰好在本身以前一位的子節點刪除消息,得到子節點變動通知後重復此步驟直至得到鎖;
  3. 執行業務代碼;
  4. 完成業務流程後,刪除對應的子節點釋放鎖。

咱們這裏直接使用curator這個開源項目提供的zookeeper分佈式鎖實現

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.0</version>
</dependency>
複製代碼
public static void main(String[] args) throws Exception {
    //建立zookeeper的客戶端
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
    client.start();

    //建立分佈式鎖, 鎖空間的根節點路徑爲/curator/lock
    InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
    mutex.acquire();
    //得到了鎖, 進行業務流程
    System.out.println("Enter mutex");
    //完成業務流程, 釋放鎖
    mutex.release();
    
    //關閉客戶端
    client.close();
}
複製代碼

參考 : www.dengshenyu.com/java/%E5%88…

隊列管理

Zookeeper能夠處理兩種類型的隊列:

  • 同步隊列
    當一個隊列的成員都聚齊時,這個隊列纔可用,不然一直等待全部成員到達。例如一個班去旅遊,看是否全部人都到齊了,到齊了就發車。例若有個大任務分解爲多個子任務,要全部子任務都完成了才能進入到下一流程。
  • 先進先出隊列 按照FIFO方式進行入隊和出隊
相關文章
相關標籤/搜索