上一節介紹了ZooKeeper的一些基礎知識,這一節主要講ZooKeeper有哪些用途。
命名服務(Name Service)
主要是作為分布式命名服務,通過調用zk的create node api,能夠很容易創建一個全局唯一的path,這個path就可以作為一個名稱。這些paht具有層級結構,非常便于理解和管理。
配置管理(Configuration Management)
配置的管理在分布式應用環境中很常見,例如同一個應用系統需要多臺Server 運行,但該應用系統的某些配置項是相同的,如果要修改這些相同的配置項,那么就必須同時修改每臺運行該系統的Server上的配置,這樣非常麻煩而且容易出錯。
像這樣的配置信息完全可以交給 Zookeeper 來管理,將配置信息保存在 Zookeeper 的某個節點中,然后將所有需要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,然后從 Zookeeper 獲取新的配置信息應用到系統中。
配置管理示意圖:
集群管理(Group Membership)
ZooKeeper的集群管理主要在兩點:監控集群是否有機器退出和加入、選舉master。
對于第一點,過去的做法通常是:監控系統通過某種手段(比如ping)定時檢測每個機器,或者每個機器自己定時向監控系統匯報“我還活著”。 這種做法可行,但是存在兩個比較明顯的問題:1)集群中機器有變動的時候,牽連修改的東西比較多。2)有一定的延時。
利用ZooKeeper有兩個特性,就可以實時另一種集群機器存活性監控系統:所有機器約定在父目錄(比如/GroupMembers
)下創建臨時目錄節點,然后監聽父目錄節點的子節點變化消息。一旦有機器掛掉,該機器與 zookeeper的連接斷開,其所創建的臨時目錄節點被刪除,所有其他機器都收到通知:某個目錄被刪除,即有一臺機器掛掉了。新機器加入也是類似。
對于第二點,在分布式環境中,相同的業務應用分布在不同的機器上,有些業務邏輯(例如一些耗時的計算,網絡I/O處理),往往只需要讓整個集群中的某一臺機器進行執行, 其余機器可以共享這個結果,這樣可以大大減少重復勞動,提高性能,于是這個master選舉便是這種場景下的碰到的主要問題。利用ZooKeeper的強一致性,能夠保證在分布式高并發情況下節點創建的全局唯一性,即:同時有多個客戶端請求創建 ·/currentMaster
節點,最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很輕易的在分布式環境中進行集群選取了。
另外,這種場景演化一下,就是動態master選舉。這就要用到EPHEMERAL_SEQUENTIAL
類型節點的特性了。
動態master選舉可以用來解決分布式系統中的單點故障。什么是分布式系統中的單點故障:通常分布式系統采用主從模式,就是一個主控機連接多個處理節點。主節點負責分發任務,從節點負責處理任務,當我們的主節點發生故障時,那么整個系統就都癱瘓了,那么我們把這種故障叫作<font color=#D2691E>單點故障</font>。
傳統的解決方案是采用一個備用節點,這個備用節點定期給當前主節點發送ping包,主節點收到ping包以后向備用節點發送回復Ack,當備用節點收到回復的時候就會認為當前主節點還活著,讓它繼續提供服務。如圖所示:
當主節點掛了,這時候備用節點收不到回復了,然后他就認為主節點掛了接替他成為主節點:
但是這種方式就是有一個隱患,就是網絡問題,來看一網絡問題會造成什么后果,如下圖所示:
也就是說我們的主節點的并沒有掛,只是在回復的時候網絡發生故障,這樣我們的備用節點同樣收不到回復,就會認為主節點掛了,然后備用節點將他的Master實例啟動起來,這樣我們的分布式系統當中就有了兩個主節點也就是---雙Master, 出現雙Master以后從節點就會將它所做的事一部分匯報給了主節點,一部分匯報給了備用節點,這樣服務就全亂了。為了防止出現這種情況,我們引入了 ZooKeeper,它雖然不能避免網絡故障,但它能夠保證每時每刻只有一個Master。
具體方案是:
1.master啟動
在引入了Zookeeper以后我們啟動了兩個主節點,"主節點-A"和"主節點-B"他們啟動以后,都向ZooKeeper去注冊一個節點(EPHEMERAL_SEQUENTIAL類型節點)。我們假設"主節點-A"鎖注冊地節點是"master-00001","主節點-B"注冊的節點是"master-00002",注冊完以后進行選舉,規定選舉序號最小的節點作為為主節點,也就是我們的"主節點-A"將會成為主節點,然后"主節點-B"將成為一個備用節點。通過這種方式就完成了對兩個Master進程的調度。
2.master故障
如果"主節點-A"掛了,這時候他所注冊的節點將被自動刪除,ZooKeeper會自動感知節點的變化,然后再次發出選舉,這時候"主節點-B"將在選舉中獲勝,替代"主節點-A"成為主節點。
3.master恢復
如果主節點恢復了,他會再次向ZooKeeper注冊一個節點,這時候他注冊的節點將會是"master-00003",ZooKeeper會感知節點的變化再次發動選舉,這時候"主節點-B"在選舉中會再次獲勝繼續擔任"主節點","主節點-A"會擔任備用節點。
分布式鎖
什么是分布式鎖?
- 一般的鎖:一般我們說的鎖是單進程多線程的鎖,在多線程并發編程中,用于線程之間的數據同步,保護共享資源的訪問
- 分布式鎖:分布式鎖指的是在分布式環境下,保護跨進程,跨主機,跨網絡的共享資源,實現互斥訪問,保證一致性
分布式鎖主要得益于ZooKeeper為我們保證了數據的一致性,即用戶只要完全相信每時每刻,zk集群中任意節點(一臺zk server)上的相同Znode的數據是一定都是相同的。
分布式鎖的架構圖:
解釋: 左邊的整個區域表示一個ZooKeeper集群,locker是ZooKeeper的一個持久節點,node_1、node_2、node_3是locker這個持久節點下面的臨時順序節點。client_1、client_2、client_n表示多個客戶端,Service表示需要互斥訪問的共享資源。
分布式鎖的總體思路
需要獲得鎖的 client 創建一個 EPHEMERAL_SEQUENTIAL 目錄節點,然后調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點,如果正是自己創建的,那么它就獲得了這個鎖,如果不是那么它就調用 exists(String path, boolean watch) 方法并監控 Zookeeper 上目錄節點列表的變化,一直到自己創建的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。
分布式隊列
Zookeeper 可以處理兩種類型的隊列:
- 隊列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。
- 當一個隊列的成員都聚齊時,這個隊列才可用,否則一直等待所有成員到達,這種是同步隊列。
第一類,和分布式鎖服務中的控制時序場景基本原理一致,入列有編號,出列按編號。實現起來也非常簡單,就是在特定的目錄下創建 SEQUENTIAL 類型的子目錄/queue_i
,這樣就能保證所有成員加入隊列時都是有編號的,出隊列時通過getChildren( )
方法可以返回當前所有的隊列中的元素,然后消費其中最小的一個,這樣就能保證 FIFO。
第二類,通常可以在/queue
這個Znode下預先建立一個/queue/num
節點,并且賦值為n(或者直接給/queue賦值為n),表示隊列大小,之后每次有隊列成員加入后,就判斷下是否已經到達隊列大小,決定是否可以開始執行 了。這種用法的典型場景是,分布式環境中,一個大任務Task A,需要在很多子任務完成(或條件就緒)情況下才能進行。這個時候,凡是其中一個子任務完成(就緒),那么就去 /taskList
下建立自己的臨時時序節點(CreateMode.EPHEMERAL_SEQUENTIAL),當/taskList
發現自己下面的子節點滿足指定個數,就可以進行下一步處理了(比如創建一個/task/start
節點)。