ZooKeeper 典型應用

ZooKeeper 典型應用

1. 數據發佈與訂閱(配置中心)

發佈與訂閱模型,即所謂的配置中心,顧名思義就是**發佈者將數據發佈到 ZK節點上,供訂閱者動態獲取數據,實現配置信息的集中式管理和動態更新。**例如全局的配置信息,地址列表等就非常適合使用。

應用在啓動的時候會主動來獲取一次配置,同時,在節點上註冊一個 Watcher,這樣一來,以後每次配置有更新的時候,都會實時通知到訂閱的客戶端,從來達到獲取最新配置信息的目的。比如:
​ 分佈式搜索服務中,索引的元信息和服務器集羣機器的節點狀態存放在 ZK的一些指定節點,供各個客戶端訂閱使用。
注意:適合數據量很小的場景,這樣數據更新可能會比較快。

  1. 所有訂閱者初次啓動時都要去zookeeper上指定的節點獲取相關的信息
  2. 獲取數據的同時還要設置監聽,監聽節點數據的變化
  3. 一旦節點數據發生改變,監聽就會被觸發,所有訂閱者(相對於zookeeper而言就是客戶端)就會收到事件的通知,從而再去獲取最新的數據
  4. 獲取完之後需要在設置監聽
舉例:

例如同一個應用系統需要多臺 PC Server 運行,但是它們運行的應用系統的某些配置項是相同的,如果要修改這些相同的配置項,那麼就必須同時修改每臺運行這個應用系統的 PC Server,這樣非常麻煩而且容易出錯。

像這樣的配置信息完全可以交給 Zookeeper 來管理,將配置信息保存在 Zookeeper 的某個目錄節點中,然後將所有需要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,然後從 Zookeeper 獲取新的配置信息應用到系統中。

應用中的具體使用
  1. 索引信息和集羣中機器節點狀態存放在zk的一些指定節點,供各個客戶端訂閱使用。
  2. 系統日誌(經過處理後的)存儲,這些日誌通常2-3天后被清除。
  3. 應用中用到的一些配置信息集中管理,在應用啓動的時候主動來獲取一次,並且在節點上註冊一個Watcher,以後每次配置有更新,實時通知到應用,獲取最新配置信息。
  4. 業務邏輯中需要用到的一些全局變量,比如一些消息中間件的消息隊列通常有個offset,這個offset存放在zk上,這樣集羣中每個發送者都能知道當前的發送進度。
  5. 系統中有些信息需要動態獲取,並且還會存在人工手動去修改這個信息。以前通常是暴露出接口,例如JMX接口,有了zk後,只要將這些信息存放到zk節點上即可。

2. 命名服務(Naming Service)

在分佈式系統中,**通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等信息。**被命名的實體通常可以是集羣中的機器,提供的服務地址,遠程對象等等——這些我們都可以統稱他們爲名字(Name)。其中較爲常見的就是一些分佈式服務框架中的服務地址列表。通過調用 ZK 提供的創建節點的 API,能夠很容易創建一個全局唯一的 path,這個 path 就可以作爲一個名稱。
阿里巴巴集團開源的分佈式服務框架 Dubbo 中使用 ZooKeeper 來作爲其命名服務,維護全局的服務地址列表。

3. 分佈式鎖

分佈式鎖,這個主要得益於 ZooKeeper 保證了數據的強一致性。鎖服務可以分爲兩類,一個是保持獨佔,另一個是控制時序。
保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把 zk 上的一個 znode 看作是一把鎖,通過 create znode 的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。

控制時序,就是所有試圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。做法和上面基本類似,只是這裏 /distribute_lock 已經預先存在,客戶端在它下面創建臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL 來指定)。Zk 的父節點(/distribute_lock)維持一份 sequence,保證子節點創建的時序性,從而也形成了每個客戶端的全局時序。

注意:

1. 控制時序需要注意避免羊羣效應:大量客戶端收到同一事件的通知,但是隻有很少一部分需要處理這一事件。

如果之前的客戶端釋放了鎖,然後觸發了其餘所有監聽的客戶端,這就會對zookeeper服務器造成壓力。

解決方案: 優化發送條件,關鍵在於僅當前一個順序號的子節點消失時才通知下一個客戶端,而不是刪除時通知所有子節點。

2. 因連接丟失而導致create 操作失敗時不清楚節點是否創建成功

這是一個非冪等的問題,所以不能再次嘗試創建。

解決方案: 在znode 的名稱中嵌入一個ID,如果客戶端出現連接對視的情況,重新連接後對鎖節點的所有子節點進行檢索,看看時候有子節點的名稱包含其ID。

3. zookeeper帶有一個Java與西安寫的生產級別的所實現,名爲writelock。

4. 隊列管理

一種是常規的先進先出隊列,另一種是要等到隊列成員聚齊之後的才統一按序執行。
對於第一種先進先出隊列,和分佈式鎖服務中的控制時序場景基本原理一致,這裏不再贅述。
第二種隊列其實是在FIFO隊列的基礎上作了一個增強。通常可以在 /queue 這個znode下預先建立一個/queue/num 節點,並且賦值爲n(或者直接給/queue賦值n),表示隊列大小,之後每次有隊列成員加入後,就判斷下是否已經到達隊列大小,決定是否可以開始執行 了。這種用法的典型場景是,分佈式環境中,一個大任務Task A,需要在很多子任務完成(或條件就緒)情況下才能進行。這個時候,凡是其中一個子任務完成(就緒),那麼就去 /taskList 下建立自己的臨時時序節點(CreateMode.EPHEMERAL_SEQUENTIAL),當 /taskList 發現自己下面的子節點滿足指定個數,就可以進行下一步按序進行處理了。

Zookeeper 可以處理兩種類型的隊列:

  1. 當一個隊列的成員都聚齊時,這個隊列纔可用,否則一直等待所有成員到達,這種是同步隊列。
  2. 隊列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。

隊列管理01

關鍵代碼. 同步隊列
`void addQueue() throws KeeperException, InterruptedException{ ``       ``zk.exists(root + "/start",true); ``       ``zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE, ``       ``CreateMode.EPHEMERAL_SEQUENTIAL); ``       ``synchronized (mutex) { ``           ``List<``String``> list = zk.getChildren(root, false); ``           ``if (list.size() < size) { ``               ``mutex.wait(); ``           ``} else { ``               ``zk.create(root + "/start", new byte[0], Ids.OPEN_ACL_UNSAFE,``                ``CreateMode.PERSISTENT); ``           ``} ``       ``} ``}`

當隊列沒滿是進入 wait(),然後會一直等待 Watch 的通知,Watch 的代碼如下:

`public void process(WatchedEvent event) { ``       ``if(event.getPath().equals(root + "/start") &&``        ``event.getType() == Event.EventType.NodeCreated){ ``           ``System.out.println("得到通知"); ``           ``super.process(event); ``           ``doAction(); ``       ``} ``   ``}`

5. 集羣管理

一個集羣可以在zookeeper的某個父節點下創建對應的子節點,用於代表集羣中的每個節點,然後leader節點在它們創建目錄節點的父目錄節點上調用 getChildren(String path, boolean watch)方法並設置 watch 爲 true,由於設置的位置是父節點,當集羣中某個子節點死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時getChildren上的 Watch 監聽將會被觸發,所以leader節點就知道已經有某臺 Server 死去了。新增 Server 也是同樣的原理。

  1. 集羣機器監控:這通常用於那種對集羣中機器狀態,機器在線率有較高要求的場景,能夠快速對集羣中機器變化作出響應。這樣的場景中,往往有一個監控系統,實時檢測集羣 機器是否存活。過去的做法通常是:監控系統通過某種手段(比如ping)定時檢測每個機器,或者每個機器自己定時向監控系統彙報“我還活着”。 這種做法可行,

    但是存在兩個比較明顯的問題:

    1. 集羣中機器有變動的時候,牽連修改的東西比較多。
    2. 有一定的延時。 利 用ZooKeeper有兩個特性,就可以實時另一種集羣機器存活性監控系統:
      • 客戶端在節點 x 上註冊一個Watcher,那麼如果 x 的子節點變化了,會通知該客戶端。
      • 創建EPHEMERAL類型的節點,一旦客戶端和服務器的會話結束或過期,那麼該節點就會消失。例 如,監控系統在 /clusterServers 節點上註冊一個Watcher,以後每動態加機器,那麼就往 /clusterServers 下創建一個 EPHEMERAL類型的節點:/clusterServers/{hostname}. 這樣,監控系統就能夠實時知道機器的增減情況,至於後續處理就是監控系統的業務了。
  2. Master選舉則是zookeeper中最爲經典的使用場景了。在 分佈式環境中,相同的業務應用分佈在不同的機器上,有些業務邏輯(例如一些耗時的計算,網絡I/O處理),往往只需要讓整個集羣中的某一臺機器進行執行, 其餘機器可以共享這個結果,這樣可以大大減少重複勞動,提高性能,於是這個master選舉便是這種場景下的碰到的主要問題。利用ZooKeeper的強一致性,能夠保證在分佈式高併發情況下節點創建的全局唯一性,即:同時有多個客戶端請求創建 /currentMaster 節點,最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很輕易的在分佈式環境中進行集羣選取了。另外,這種場景演化一下,就是動態Master選舉。這就要用到 EPHEMERAL_SEQUENTIAL類型節點的特性了。上 文中提到,所有客戶端創建請求,最終只有一個能夠創建成功。在這裏稍微變化下,就是允許所有請求都能夠創建成功,但是得有個創建順序,於是所有的請求最終 在ZK上創建結果的一種可能情況是這樣: /currentMaster/{sessionId}-1 , /currentMaster/{sessionId}-2 , /currentMaster/{sessionId}-3 …… 每次選取序列號最小的那個機器作爲Master,如果這個機器掛了,由於他創建的節點會馬上小時,那麼之後最小的那個機器就是Master了。

應用中的具體使用
  1. 在搜索系統中,如果集羣中每個機器都生成一份全量索引,不僅耗時,而且不能保證彼此之間索引數據一致。因此讓集羣中的Master來進行全量索引的生成, 然後同步到集羣中其它機器。
  2. 另外,Master選舉的容災措施是,可以隨時進行手動指定master,就是說應用在zk在無法獲取master信息時,可以通過比如http方式,向 一個地方獲取master。

6. 分佈通知/協調

ZooKeeper 中特有watcher註冊與異步通知機制,能夠很好的實現分佈式環境下不同系統之間的通知與協調,實現對數據變更的實時處理。使用方法通常是不同系統都對 ZK上同一個znode進行註冊,監聽znode的變化(包括znode本身內容及子節點的),其中一個系統update了znode,那麼另一個系統能 夠收到通知,並作出相應處理。

應用中的具體使用
  1. 另一種心跳檢測機制:檢測系統和被檢測系統之間並不直接關聯起來,而是通過zk上某個節點關聯,大大減少系統耦合。
  2. 另一種系統調度模式:某系統有控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工作。管理人員在控制檯作的一些操作,實際上是修改 了ZK上某些節點的狀態,而zk就把這些變化通知給他們註冊Watcher的客戶端,即推送系統,於是,作出相應的推送任務。
  3. 另一種工作彙報模式:一些類似於任務分發系統,子任務啓動後,到zk來註冊一個臨時節點,並且定時將自己的進度進行彙報(將進度寫回這個臨時節點),這樣任務管理者就能夠實時知道任務進度。總之,使用zookeeper來進行分佈式通知和協調能夠大大降低系統之間的耦合。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章