1. 程式人生 > >ZooKeeper的典型應用場景

ZooKeeper的典型應用場景

拉取 ons 執行 全局 進行 創建失敗 消息通知 防止 成了

《從Paxos到Zookeeper 分布式一致性原理與實踐》讀書筆記

本文:總結腦圖地址:腦圖

前言

所有的典型應用場景,都是利用了ZK的如下特性:

  1. 強一致性:在高並發情況下,能夠保證節點的創建一定是全局唯一的。
  2. Watcher機制和異步通知:可以對指定節點加上監聽,當節點變更時,會收到ZK的通知。

數據發布訂閱(配置中心)

常見的應用就是配置中心,發布者將數據發布到ZooKeeper的一個或多個節點上,供訂閱者進行數據訂閱,進而達到動態獲取數據的目的,實現配置信息的集中式管理和數據的動態更新。

發布訂閱系統一般有兩種設計模式:分別是推(Push)模式和拉(Pull)模式。在推模式下,服務端主動將數據更新發送給所有訂閱的客戶端;而拉模式則是由客戶端主動發起請求來獲取最新數據,通常客戶端都采用定時進行輪詢拉取的方式。

ZooKeeper采用的是推拉相結合的方式:客戶端向服務端註冊自己需要關註的節點,一旦該節點的數據發送變更,那麽服務端就會向相應的客戶端發送Watcher事件通知,客戶端接收到這個消息通知之後,需主動到服務端獲取最新的數據。

通常,放到配置中心上的數據都具有如下特點:

  1. 數據量通常比較小。
  2. 數據內容在運行時會發生動態變化。
  3. 集群中各機器共享,配置一致。

命名服務

是指通過指定的名字來獲取資源或者服務的地址,提供者的信息。利用Zookeeper很容易創建一個全局的路徑,而這個路徑就可以作為一個名字,它可以指向集群中的集群,提供的服務的地址,遠程對象等。簡單來說使用Zookeeper做命名服務就是用路徑作為名字,路徑上的數據就是其名字指向的實體。

阿裏巴巴集團開源的分布式服務框架Dubbo中使用ZooKeeper來作為其命名服務,維護全局的服務地址列表。在Dubbo實現中:

服務提供者在啟動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的發布。

服務消費者啟動的時候,訂閱/dubbo/{serviceName}/providers目錄下的提供者URL地址, 並向/dubbo/{serviceName} /consumers目錄下寫入自己的URL地址。

註意,所有向ZK上註冊的地址都是臨時節點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。另外,Dubbo還有針對服務粒度的監控,方法是訂閱/dubbo/{serviceName}

目錄下所有提供者和消費者的信息。

分布式協調/通知

  1. 分布式定時任務:
  • 公平的方式:同時向指定目錄下創建臨時非順序節點,創建成功的節點即獲得執行任務的資格
  • 非公平的方式:同時向指定目錄下創建臨時順序節點,節點順序最小的節點就獲得執行權限。
  1. 心跳檢測:
    基於ZooKeeper的臨時節點特性,可以讓不同的機器都在ZooKeeper的一個指定節點下創建臨時子節點,不同的機器之間可以根據這個臨時節點來判斷對應的客戶端機器是否存活。通過這種方式,檢測系統和被檢測系統之間並不需要直接相關聯,而是通過ZooKeeper上的某個節點進行關聯,大大減少了系統耦合。

  2. HA:同心跳檢測。

分布式鎖

使用ZK構建分布式鎖都是利用了ZK能夠保證高並發情況下,子節點創建的唯一性。

非公平獨占鎖

在特定目錄下,創建臨時子節點,創建成功,則表示獲得鎖。創建失敗,直接返回加鎖失敗或者監聽指定節點,然後等待加鎖成功的節點釋放鎖後自己再嘗試獲取鎖。

公平獨占鎖

以加鎖key名稱創建永久節點,然後爭搶鎖的節點都在該永久節點下創建臨時有序節點,序號最小的節點即獲得鎖。序號非最小的節點,監聽前一個節點,而非全部節點,收到通知後,再驗證自己是否是序號最小的節點,如果是,則表示獲得鎖,否則重新監聽前一個節點。

共享鎖(S鎖)

所有嘗試加鎖的節點都在指定目錄下創建臨時順序節點,形如/shared_lock/[hostname]-請求類型-序號的臨時順序節點,例如/shared_lock/192.168.0.1-R-00000001,就代表了一個共享鎖;/shared_lock/192.168.0.1-W-00000001,就代表是一個寫鎖。

根據共享鎖的定義,不同的事務都可以同時對同一個數據對象進行讀取操作,而更新操作必須在當前沒有任何事務進行讀寫操作的情況下進行。基於這個原則,我們來看看如何通過ZooKeeper的節點來確定分布式讀寫順序:

  1. 創建完節點後,獲取/shared_lock節點下的所有子節點,並確定自己的節點序號在所有子節點中的順序。
  2. 對於讀請求
  • 如果沒有比自己序號小的子節點,或是所有比自己序號小的子節點都是讀請求,那麽表名自己已經成功獲取到了共享鎖,同時開始執行讀取邏輯。
  • 如果比自己序號小的子節點有寫請求,那麽就需要進入等待。
  1. 對於寫請求:如果自己不是序號最小的子節點,那麽就需要進入等待。
  2. 接收到Watcher通知後,重復步驟1。

Master選舉

利用ZooKeeper的強一致性,能夠很好地保證在分布式高並發情況下節點的創建一定能夠保證全局唯一性,即ZooKeeper將會保證客戶端無法重復創建一個已經存在的數據節點。當多臺機器同時爭搶Master角色時,大家都往指定目錄下創建臨時節點,在這個過程中,只有一個客戶端能夠成功創建這個節點,那麽這個客戶端所在的機器就稱為了Master。同時,沒有創建成功的客戶端都在該節點上註冊一個監聽事件,用於監控當前的Master機器是否存活,一旦發現當前的Master掛了,那麽其余的客戶端將會重新進行Master選舉。

分布式隊列

FIFO先入先出隊列

序號最小的先出隊列:

  1. 所有入隊列的節點都往指定目錄下創建順序有序節點。
  2. 通過調用getChildren()接口來獲取該節點下的所有子節點,即獲取隊列中所有的元素。
  3. 確定自己的節點序號在所有子節點中的順序
  4. 如果自己不是序號最小的子節點,那就需要進入等待,同時向比自己序號小的最後一個節點註冊Watcher監聽。

Barrier :分布式屏障

分布式Barrier規定了一個隊列的元素必須都集聚後才能統一進行安排,否則一直等待。這往往出現在那些大規模分布式並行計算的應用場景上:最終的合並計算需要基於很多並行計算的子結果來進行。 大致的設計思路如下:

  • 開始時,/queue_barrier節點是一個已經存在的默認節點,並且將其節點的數據內容賦值為一個數字n來代表Barrier值,例如n=10表示只有當/queue_barrier節點下的子節點個數達到10後,才會打開Barrier。

  • 所有客戶端都會到/queue_barrier節點下創建一個臨時節點。

  • 創建成功後,通過調用getData()接口獲取/queue_barrier節點的數據內容:10。
  • 通過調用getChildren()接口獲取/queue_barrier節點下的所有子節點,即獲取隊列中的所有元素,同時註冊對子節點列表變更的Watcher監聽。
  • 統計子節點的個數。
  • 如果子節點個數還不足10個,那就需要進入等待。
  • 接收到Watcher通知後,重復步驟2。

註意

對於涉及到順序有序節點,排序等待的場景。不需要監聽父節點或者其他全部兄弟節點,只要監聽序號比自己小的第一個節點即可,這樣可以防止接收到大量無用的Watcher通知。

ZooKeeper的典型應用場景