ZooKeeper應用理論及其應用場景

 

ZooKeeper Client API
ZooKeeper Client Library提供了豐富直觀的API供用戶程序使用,下面是一些經常使用的API:
● create(path, data, flags): 建立一個ZNode, path是其路徑,data是要存儲在該ZNode上的數據,flags經常使用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
● delete(path, version): 刪除一個ZNode,能夠經過version刪除指定的版本, 若是version是-1的話,表示刪除全部的版本
● exists(path, watch): 判斷指定ZNode是否存在,並設置是否Watch這個ZNode。這裏若是要設置Watcher的話,Watcher是在建立ZooKeeper實例時指定的,若是要設置特定的Watcher的話,能夠調用另外一個重載版本的exists(path, watcher)。如下幾個帶watch參數的API也都相似
● getData(path, watch): 讀取指定ZNode上的數據,並設置是否watch這個ZNode
● setData(path, watch): 更新指定ZNode的數據,並設置是否Watch這個ZNode
● getChildren(path, watch): 獲取指定ZNode的全部子ZNode的名字,並設置是否Watch這個ZNode
● sync(path): 把全部在sync以前的更新操做都進行同步,達到每一個請求都在半數以上的ZooKeeper Server上生效。path參數目前沒有用
● setAcl(path, acl): 設置指定ZNode的Acl信息
● getAcl(path): 獲取指定ZNode的Acl信息node


ZooKeeper典型應用場景
1. 名字服務(NameService)
分佈式應用中,一般須要一套完備的命令機制,既能產生惟一的標識,又方便人識別和記憶。 咱們知道,每一個ZNode均可以由其路徑惟一標識,路徑自己也比較簡潔直觀,另外ZNode上還能夠存儲少許數據,這些都是實現統一的NameService的基礎。下面以在HDFS中實現NameService爲例,來講明實現NameService的基本布驟:
● 目標:經過簡單的名字來訪問指定的HDFS機羣
● 定義命名規則:這裏要作到簡潔易記憶。下面是一種可選的方案: [serviceScheme://][zkCluster]-[clusterName],好比hdfs://lgprc-example/表示基於lgprc ZooKeeper集羣的用來作example的HDFS集羣
● 配置DNS映射: 將zkCluster的標識lgprc經過DNS解析到對應的ZooKeeper集羣的地址
● 建立ZNode: 在對應的ZooKeeper上建立/NameService/hdfs/lgprc-example結點,將HDFS的配置文件存儲於該結點下
● 用戶程序要訪問hdfs://lgprc-example/的HDFS集羣,首先經過DNS找到lgprc的ZooKeeper機羣的地址,而後在ZooKeeper的/NameService/hdfs/lgprc-example結點中讀取到HDFS的配置,進而根據獲得的配置,獲得HDFS的實際訪問入口
2. 配置管理(Configuration Management)
在分佈式系統中,常會遇到這樣的場景: 某個Job的不少個實例在運行,它們在運行時大多數配置項是相同的,若是想要統一改某個配置,一個個實例去改,是比較低效,也是比較容易出錯的方式。經過ZooKeeper能夠很好的解決這樣的問題,下面的基本的步驟:
● 將公共的配置內容放到ZooKeeper中某個ZNode上,好比/service/common-conf
● 全部的實例在啓動時都會傳入ZooKeeper集羣的入口地址,而且在運行過程當中Watch /service/common-conf這個ZNode
● 若是集羣管理員修改了了common-conf,全部的實例都會被通知到,根據收到的通知更新本身的配置,並繼續Watch /service/common-conf
3. 組員管理(Group Membership)
在典型的Master-Slave結構的分佈式系統中,Master須要做爲「總管」來管理全部的Slave, 當有Slave加入,或者有Slave宕機,Master都須要感知到這個事情,而後做出對應的調整,以便不影響整個集羣對外提供服務。以Hbase爲例,HMaster管理了全部的RegionServer,當有新的RegionServer加入的時候,HMaster須要分配一些Region到該RegionServer上去,讓其提供服務;當有RegionServer宕機時,HMaster須要將該RegionServer以前服務的Region都從新分配到當前正在提供服務的其它RegionServer上,以便不影響客戶端的正常訪問。下面是這種場景下使用ZooKeeper的基本步驟:
● Master在ZooKeeper上建立/service/slaves結點,並設置對該結點的Watcher
● 每一個Slave在啓動成功後,建立惟一標識本身的臨時性(Ephemeral)結點/service/slaves/${slave_id},並將本身地址(ip/port)等相關信息寫入該結點
● Master收到有新子結點加入的通知後,作相應的處理
● 若是有Slave宕機,因爲它所對應的結點是臨時性結點,在它的Session超時後,ZooKeeper會自動刪除該結點
● Master收到有子結點消失的通知,作相應的處理
4. 簡單互斥鎖(Simple Lock)
咱們知識,在傳統的應用程序中,線程、進程的同步,均可以經過操做系統提供的機制來完成。可是在分佈式系統中,多個進程之間的同步,操做系統層面就無能爲力了。這時候就須要像ZooKeeper這樣的分佈式的協調(Coordination)服務來協助完成同步,下面是用ZooKeeper實現簡單的互斥鎖的步驟,這個能夠和線程間同步的mutex作類比來理解:
● 多個進程嘗試去在指定的目錄下去建立一個臨時性(Ephemeral)結點 /locks/my_lock
● ZooKeeper能保證,只會有一個進程成功建立該結點,建立結點成功的進程就是搶到鎖的進程,假設該進程爲A
● 其它進程都對/locks/my_lock進行Watch
● 當A進程再也不須要鎖,能夠顯式刪除/locks/my_lock釋放鎖;或者是A進程宕機後Session超時,ZooKeeper系統自動刪除/locks/my_lock結點釋放鎖。此時,其它進程就會收到ZooKeeper的通知,並嘗試去建立/locks/my_lock搶鎖,如此循環反覆
5. 互斥鎖(Simple Lock without Herd Effect)
上一節的例子中有一個問題,每次搶鎖都會有大量的進程去競爭,會形成羊羣效應(Herd Effect),爲了解決這個問題,咱們能夠經過下面的步驟來改進上述過程:
● 每一個進程都在ZooKeeper上建立一個臨時的順序結點(Ephemeral Sequential) /locks/lock_${seq}
● ${seq}最小的爲當前的持鎖者(${seq}是ZooKeeper生成的Sequenctial Number)
● 其它進程都對只watch比它次小的進程對應的結點,好比2 watch 1, 3 watch 2, 以此類推
● 當前持鎖者釋放鎖後,比它次大的進程就會收到ZooKeeper的通知,它成爲新的持鎖者,如此循環反覆
這裏須要補充一點,一般在分佈式系統中用ZooKeeper來作Leader Election(選主)就是經過上面的機制來實現的,這裏的持鎖者就是當前的「主」。
6. 讀寫鎖(Read/Write Lock)
咱們知道,讀寫鎖跟互斥鎖相比不一樣的地方是,它分紅了讀和寫兩種模式,多個讀能夠併發執行,但寫和讀、寫都互斥,不能同時執行行。利用ZooKeeper,在上面的基礎上,稍作修改也能夠實現傳統的讀寫鎖的語義,下面是基本的步驟:
● 每一個進程都在ZooKeeper上建立一個臨時的順序結點(Ephemeral Sequential) /locks/lock_${seq}
● ${seq}最小的一個或多個結點爲當前的持鎖者,多個是由於多個讀能夠併發
● 須要寫鎖的進程,Watch比它次小的進程對應的結點
● 須要讀鎖的進程,Watch比它小的最後一個寫進程對應的結點
● 當前結點釋放鎖後,全部Watch該結點的進程都會被通知到,他們成爲新的持鎖者,如此循環反覆
7. 屏障(Barrier)
在分佈式系統中,屏障是這樣一種語義: 客戶端須要等待多個進程完成各自的任務,而後才能繼續往前進行下一步。下用是用ZooKeeper來實現屏障的基本步驟:
● Client在ZooKeeper上建立屏障結點/barrier/my_barrier,並啓動執行各個任務的進程
● Client經過exist()來Watch /barrier/my_barrier結點
● 每一個任務進程在完成任務後,去檢查是否達到指定的條件,若是沒達到就啥也不作,若是達到了就把/barrier/my_barrier結點刪除
● Client收到/barrier/my_barrier被刪除的通知,屏障消失,繼續下一步任務
8. 雙屏障(Double Barrier)
雙屏障是這樣一種語義: 它能夠用來同步一個任務的開始和結束,當有足夠多的進程進入屏障後,纔開始執行任務;當全部的進程都執行完各自的任務後,屏障才撤銷。下面是用ZooKeeper來實現雙屏障的基本步驟:
進入屏障:
● Client Watch /barrier/ready結點, 經過判斷該結點是否存在來決定是否啓動任務
● 每一個任務進程進入屏障時建立一個臨時結點/barrier/process/${process_id},而後檢查進入屏障的結點數是否達到指定的值,若是達到了指定的值,就建立一個/barrier/ready結點,不然繼續等待
● Client收到/barrier/ready建立的通知,就啓動任務執行過程
離開屏障:
● Client Watch /barrier/process,若是其沒有子結點,就能夠認爲任務執行結束,能夠離開屏障
● 每一個任務進程執行任務結束後,都須要刪除本身對應的結點/barrier/process/${process_id}服務器

應用場景網絡

數據發佈與訂閱(配置中心)
發佈與訂閱模型,即所謂的配置中心,顧名思義就是發佈者將數據發佈到ZK節點上,供訂閱者動態獲取數據,實現配置信息的集中式管理和動態更新。例如全局的配置信息,服務式服務框架的服務地址列表等就很是適合使用。應用中用到的一些配置信息放到ZK上進行集中管理。這類場景一般是這樣:應用在啓動的時候會主動來獲取一次配置,同時,在節點上註冊一個Watcher,這樣一來,之後每次配置有更新的時候,都會實時通知到訂閱的客戶端,歷來達到獲取最新配置信息的目的。
分佈式搜索服務中,索引的元信息和服務器集羣機器的節點狀態存放在ZK的一些指定節點,供各個客戶端訂閱使用。
分佈式日誌收集系統。這個系統的核心工做是收集分佈在不一樣機器的日誌。收集器一般是按照應用來分配收集任務單元,所以須要在ZK上建立一個以應用名做爲path的節點P,並將這個應用的全部機器ip,以子節點的形式註冊到節點P上,這樣一來就可以實現機器變更的時候,可以實時通知到收集器調整任務分配。
系統中有些信息須要動態獲取,而且還會存在人工手動去修改這個信息的發問。一般是暴露出接口,例如JMX接口,來獲取一些運行時的信息。引入ZK以後,就不用本身實現一套方案了,只要將這些信息存放到指定的ZK節點上便可。
注意:在上面提到的應用場景中,有個默認前提是:數據量很小,可是數據更新可能會比較快的場景。負載均衡這裏說的負載均衡是指軟負載均衡。在分佈式環境中,爲了保證高可用性,一般同一個應用或同一個服務的提供方都會部署多份,達到對等服務。而消費者就需要在這些對等的服務器中選擇一個來執行相關的業務邏輯,其中比較典型的是消息中間件中的生產者,消費者負載均衡。消息中間件中發佈者和訂閱者的負載均衡,linkedin開源的KafkaMQ和阿里開源的metaq都是經過zookeeper來作到生產者、消費者的負載均衡。這裏以metaq爲例如講下:
生產者負載均衡:metaq發送消息的時候,生產者在發送消息的時候必須選擇一臺broker上的一個分區來發送消息,所以metaq在運行過程當中,會把全部broker和對應的分區信息所有註冊到ZK指定節點上,默認的策略是一個依次輪詢的過程,生產者在經過ZK獲取分區列表以後,會按照brokerId和partition的順序排列組織成一個有序的分區列表,發送的時候按照從頭至尾循環往復的方式選擇一個分區來發送消息。
消費負載均衡:
在消費過程當中,一個消費者會消費一個或多個分區中的消息,可是一個分區只會由一個消費者來消費。MetaQ的消費策略是:
每一個分區針對同一個group只掛載一個消費者。
若是同一個group的消費者數目大於分區數目,則多出來的消費者將不參與消費。
若是同一個group的消費者數目小於分區數目,則有部分消費者須要額外承擔消費任務。
在某個消費者故障或者重啓等狀況下,其餘消費者會感知到這一變化(經過 zookeeper watch消費者列表),而後從新進行負載均衡,保證全部的分區都有消費者進行消費。
命名服務(Naming Service)
命名服務也是分佈式系統中比較常見的一類場景。在分佈式系統中,經過使用命名服務,客戶端應用可以根據指定名字來獲取資源或服務的地址,提供者等信息。被命名的實體一般能夠是集羣中的機器,提供的服務地址,遠程對象等等——這些咱們均可以統稱他們爲名字(Name)。其中較爲常見的就是一些分佈式服務框架中的服務地址列表。經過調用ZK提供的建立節點的API,可以很容易建立一個全局惟一的path,這個path就能夠做爲一個名稱。阿里巴巴集團開源的分佈式服務框架Dubbo中使用ZooKeeper來做爲其命名服務,維護全局的服務地址列表,點擊這裏查看Dubbo開源項目。在Dubbo實現中:
服務提供者在啓動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers目錄下寫入本身的URL地址,這個操做就完成了服務的發佈。
服務消費者啓動的時候,訂閱/dubbo/${serviceName}/providers目錄下的提供者URL地址, 並向/dubbo/${serviceName} /consumers目錄下寫入本身的URL地址。
注意,全部向ZK上註冊的地址都是臨時節點,這樣就可以保證服務提供者和消費者可以自動感應資源的變化。
另外,Dubbo還有針對服務粒度的監控,方法是訂閱/dubbo/${serviceName}目錄下全部提供者和消費者的信息。
分佈式通知/協調
ZooKeeper中特有watcher註冊與異步通知機制,可以很好的實現分佈式環境下不一樣系統之間的通知與協調,實現對數據變動的實時處理。使用方法一般是不一樣系統都對ZK上同一個znode進行註冊,監聽znode的變化(包括znode自己內容及子節點的),其中一個系統update了znode,那麼另外一個系統可以收到通知,並做出相應處理另外一種心跳檢測機制:檢測系統和被檢測系統之間並不直接關聯起來,而是經過zk上某個節點關聯,大大減小系統耦合。
另外一種系統調度模式:某系統有控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工做。管理人員在控制檯做的一些操做,其實是修改了ZK上某些節點的狀態,而ZK就把這些變化通知給他們註冊Watcher的客戶端,即推送系統,因而,做出相應的推送任務。
另外一種工做彙報模式:一些相似於任務分發系統,子任務啓動後,到zk來註冊一個臨時節點,而且定時將本身的進度進行彙報(將進度寫回這個臨時節點),這樣任務管理者就可以實時知道任務進度。
總之,使用zookeeper來進行分佈式通知和協調可以大大下降系統之間的耦合
集羣管理與Master選舉
集羣機器監控:這一般用於那種對集羣中機器狀態,機器在線率有較高要求的場景,可以快速對集羣中機器變化做出響應。這樣的場景中,每每有一個監控系統,實時檢測集羣機器是否存活。過去的作法一般是:監控系統經過某種手段(好比ping)定時檢測每一個機器,或者每一個機器本身定時向監控系統彙報「我還活着」。 這種作法可行,可是存在兩個比較明顯的問題:
集羣中機器有變更的時候,牽連修改的東西比較多。
有必定的延時。
利用ZooKeeper有兩個特性,就能夠實時另外一種集羣機器存活性監控系統:
客戶端在節點 x 上註冊一個Watcher,那麼若是 x?的子節點變化了,會通知該客戶端。
建立EPHEMERAL類型的節點,一旦客戶端和服務器的會話結束或過時,那麼該節點就會消失。
例如,監控系統在 /clusterServers 節點上註冊一個Watcher,之後每動態加機器,那麼就往 /clusterServers 下建立一個 EPHEMERAL類型的節點:/clusterServers/{hostname}. 這樣,監控系統就可以實時知道機器的增減狀況,至於後續處理就是監控系統的業務了。
Master選舉則是zookeeper中最爲經典的應用場景了。
在分佈式環境中,相同的業務應用分佈在不一樣的機器上,有些業務邏輯(例如一些耗時的計算,網絡I/O處理),每每只須要讓整個集羣中的某一臺機器進行執行,其他機器能夠共享這個結果,這樣能夠大大減小重複勞動,提升性能,因而這個master選舉即是這種場景下的碰到的主要問題。
利用ZooKeeper的強一致性,可以保證在分佈式高併發狀況下節點建立的全局惟一性,即:同時有多個客戶端請求建立 /currentMaster 節點,最終必定只有一個客戶端請求可以建立成功。利用這個特性,就能很輕易的在分佈式環境中進行集羣選取了。
另外,這種場景演化一下,就是動態Master選舉。這就要用到?EPHEMERAL_SEQUENTIAL類型節點的特性了。
上文中提到,全部客戶端建立請求,最終只有一個可以建立成功。在這裏稍微變化下,就是容許全部請求都可以建立成功,可是得有個建立順序,因而全部的請求最終在ZK上建立結果的一種可能狀況是這樣: /currentMaster/{sessionId}-1 ,?/currentMaster/{sessionId}-2 ,?/currentMaster/{sessionId}-3 ….. 每次選取序列號最小的那個機器做爲Master,若是這個機器掛了,因爲他建立的節點會立刻小時,那麼以後最小的那個機器就是Master了。在搜索系統中,若是集羣中每一個機器都生成一份全量索引,不只耗時,並且不能保證彼此之間索引數據一致。所以讓集羣中的Master來進行全量索引的生成,而後同步到集羣中其它機器。另外,Master選舉的容災措施是,能夠隨時進行手動指定master,就是說應用在zk在沒法獲取master信息時,能夠經過好比http方式,向一個地方獲取master。
在Hbase中,也是使用ZooKeeper來實現動態HMaster的選舉。在Hbase實現中,會在ZK上存儲一些ROOT表的地址和HMaster的地址,HRegionServer也會把本身以臨時節點(Ephemeral)的方式註冊到Zookeeper中,使得HMaster能夠隨時感知到各個HRegionServer的存活狀態,同時,一旦HMaster出現問題,會從新選舉出一個HMaster來運行,從而避免了HMaster的單點問題
分佈式鎖
分佈式鎖:這個主要得益於ZooKeeper爲咱們保證了數據的強一致性。鎖服務能夠分爲兩類,一個是保持獨佔,另外一個是控制時序。
所謂保持獨佔,就是全部試圖來獲取這個鎖的客戶端,最終只有一個能夠成功得到這把鎖。一般的作法是把zk上的一個znode看做是一把鎖,經過create znode的方式來實現。全部客戶端都去建立 /distribute_lock 節點,最終成功建立的那個客戶端也即擁有了這把鎖。
控制時序,就是全部視圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。作法和上面基本相似,只是這裏 /distribute_lock 已經預先存在,客戶端在它下面建立臨時有序節點(這個能夠經過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL來指定)。Zk的父節點(/distribute_lock)維持一份sequence,保證子節點建立的時序性,從而也造成了每一個客戶端的全局時序。
分佈式隊列
隊列方面:簡單地講有兩種,一種是常規的先進先出隊列,另外一種是要等到隊列成員聚齊以後的才統一按序執行。對於第一種先進先出隊列,和分佈式鎖服務中的控制時序場景基本原理一致,這裏再也不贅述。
第二種隊列實際上是在FIFO隊列的基礎上做了一個加強。一般能夠在 /queue 這個znode下預先創建一個/queue/num 節點,而且賦值爲n(或者直接給/queue賦值n),表示隊列大小,以後每次有隊列成員加入後,就判斷下是否已經到達隊列大小,決定是否能夠開始執行了。這種用法的典型場景是,分佈式環境中,一個大任務Task A,須要在不少子任務完成(或條件就緒)狀況下才能進行。這個時候,凡是其中一個子任務完成(就緒),那麼就去 /taskList 下創建本身的臨時時序節點(CreateMode.EPHEMERAL_SEQUENTIAL),當 /taskList 發現本身下面的子節點知足指定個數,就能夠進行下一步按序進行處理了。session

相關文章
相關標籤/搜索