ZooKeeper 是一個高可用的分布式數(shù)據(jù)管理與系統(tǒng)協(xié)調(diào)框架炒刁《鞴粒基于對 Paxos 算法的實現(xiàn),使該框架保證了分布式環(huán)境中數(shù)據(jù)的強一致性翔始,也正是基于這樣的特性罗心,使得 ZooKeeper 解決很多分布式問題。網(wǎng)上對 ZK 的應用場景也有丌少介紹城瞎,本文將結(jié)合作者身邊的項目例子渤闷,系統(tǒng)地對ZK 的應用場景進行一個分門歸類的介紹。
值得注意的是脖镀,ZK 并非天生就是為這些應用場景設(shè)計的飒箭,都是后來眾多開發(fā)者根據(jù)其框架的特性,利用其提供的一系列 API 接口(或者稱為原語集)蜒灰,摸索出來的典型使用方法弦蹂。因此,也非常歡迎讀者分享你在 ZK 使用上的奇技淫巧卷员。
什么是Zookeeper
zookeeper是一個高效的分布式協(xié)調(diào)服務盈匾,它暴露出一些公用服務腾务,比如命名毕骡、配置管理、同步控制岩瘦、群組服務等未巫。我們可以使用ZK來實現(xiàn)比如達成共識、集群管理启昧、leader選舉等叙凡。
zookeeper是一個高可用的分布式管理與協(xié)調(diào)框架,基于ZAB算法(原子消息廣播協(xié)議)的實現(xiàn)密末。該框架能夠保證分布式環(huán)境中數(shù)據(jù)的一致性握爷。也正式基于這樣的特性,使得zookeeper成為了解決分布式一致性問題的利器严里。
順序一致性:從一個客戶端發(fā)起事務請求新啼,最終將會嚴格的按照其發(fā)起的順序被應用到zookeeper中去。
原子性:所有事務請求的處理結(jié)果在整個集群中所有機器上的應用情況是一致的刹碾,也就是說燥撞,要么整個集群所有的機器都成功應用了某一個事務,要么就沒有應用,一定不會出現(xiàn)部分機器應用了該事務物舒,而另一部分沒有應用的情況色洞。
單一視圖:無論客戶端連接的是那個服務器,起看到服務端的數(shù)據(jù)模型都是一致的冠胯。
可靠性:一旦服務器成功的應用了一個事務火诸,并完成對客戶端的響應,那么該事務所引起的服務端狀態(tài)將會被一致保留下來涵叮。除非有另外一個事務對其修改惭蹂。
實時性:通常所說的實時性就是指一旦事務被成功應用,那么客戶端就能立刻從服務器上獲取變更后的新數(shù)據(jù)割粮,zookeeper僅僅能保證在一段時間內(nèi)盾碗,客戶端最終一定能從服務器讀取最新的數(shù)據(jù)狀態(tài)。
Zookeeper設(shè)計目標
目標1:簡單數(shù)據(jù)結(jié)構(gòu)舀瓢。zookeeper就是以簡單的樹形結(jié)構(gòu)來進行相互協(xié)調(diào)的(也叫樹形名字空間)
目標2:構(gòu)建集群廷雅。一般zookeeper集群通常由一組機器構(gòu)成,一般3-5臺機器就可以組成一個zookeeper集群京髓,只要集群中超過半數(shù)以上的機器能夠正常工作航缀,那么整個集群就能夠正常對外提供服務。
目標3:順序訪問堰怨。對于來自每個客戶端的每個請求芥玉,zookeeper都會分配一個全局唯一的遞增編號,這個編號反應了所以事務操作的先后順序备图,應用程序可以使用zookeeper的這個特性來實現(xiàn)更高層次的同步灿巧。
目標4:高性能。由于zookeeper將全量數(shù)據(jù)存儲在內(nèi)存中揽涮,并直接服務與所有的非事務請求抠藕,因此尤其是在讀操作為主的場景下性能非常突出。在JMater壓力測試下(100%讀請求場景下)蒋困,其結(jié)果大約在12-13W的QPS盾似。
數(shù)據(jù)發(fā)布與訂閱(配置中心)
發(fā)布與訂閱模型,即所謂的配置中心雪标,顧名思義就是發(fā)布者將數(shù)據(jù)發(fā)布到 ZK 節(jié)點上砰奕,供訂閱者動態(tài)獲取數(shù)據(jù)忆谓,實現(xiàn)配置信息的集中式管理和動態(tài)更新。例如全局的配置信息,服務式服務框架的服務地址列表等就非常適合使用庐船。
1俏让、應用中用到的一些配置信息放到 ZK 上進行集中管理晶渠。這類場景通常是這樣:應用在啟動的時候會主動來獲取一次配置侦镇,同時,在節(jié)點上注冊一個 Watcher,這樣一來拟蜻,以后每次配置有更新的時候绎签,都會實時通知到訂閱的客戶端,從來達到獲取最新配置信息的目的酝锅。
2诡必、分布式搜索服務中,索引的元信息和服務器集群機器的節(jié)點狀態(tài)存放在 ZK 的一些指定節(jié)點搔扁,供各個客戶端訂閱使用爸舒。
3、分布式日志收集系統(tǒng)稿蹲。這個系統(tǒng)的核心工作是收集分布在不同機器的日志扭勉。收集器通常是按照應用來分配收集任務單元,因此需要在 ZK 上創(chuàng)建一個以應用名作為 path 的節(jié)點 P苛聘,并將這個應用的所有機器 ip涂炎,以子節(jié)點的形式注冊到節(jié)點 P 上,這樣一來就能夠?qū)崿F(xiàn)機器變動的時候设哗,能夠?qū)崟r通知到收集器調(diào)整任務分配唱捣。
4、系統(tǒng)中有些信息需要動態(tài)獲取网梢,并且還會存在人工手動去修改這個信息的發(fā)問震缭。通常是暴露出接口,例如 JMX 接口战虏,來獲取一些運行時的信息拣宰。引入 ZK 之后,就不用自己實現(xiàn)一套方案了活烙,只要將這些信息存放到指定的 ZK 節(jié)點上即可徐裸。
注意:在上面提到的應用場景中遣鼓,有個默認前提是:數(shù)據(jù)量很小啸盏,但是數(shù)據(jù)更新可能會比較快的場景。
負載均衡
這里說的負載均衡是指軟負載均衡骑祟。在分布式環(huán)境中回懦,為了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份次企,達到對等服務怯晕。而消費者就須要在這些對等的服務器中選擇一個來執(zhí)行相關(guān)的業(yè)務邏輯,其中比較典型的是消息中間件中的生產(chǎn)者缸棵,消費者負載均衡舟茶。
消息中間件中發(fā)布者和訂閱者的負載均衡,linkedin 開源的 KafkaMQ 和阿里開源的 metaq 都是通過 zookeeper 來做到生產(chǎn)者、消費者的負載均衡吧凉。這里以 metaq 為例如講下:
生產(chǎn)者負載均衡:
metaq 發(fā)送消息的時候隧出,生產(chǎn)者在發(fā)送消息的時候必須選擇一臺 broker 上的一個分區(qū)來發(fā)送消息,因此metaq 在運行過程中阀捅,會把所有 broker和對應的分區(qū)信息全部注冊到 ZK 指定節(jié)點上胀瞪,默認的策略是一個依次輪詢的過程,生產(chǎn)者在通過 ZK 獲取分區(qū)列表之后饲鄙,會按照 brokerId 和partition 的順序排列組織成一個有序的分區(qū)列表凄诞,發(fā)送的時候按照從頭到尾循環(huán)往復的方式選擇一個分區(qū)來發(fā)送消息。
消費負載均衡:
在消費過程中忍级,一個消費者會消費一個或多個分區(qū)中的消息帆谍,但是一個分區(qū)只會由一個消費者來消費。MetaQ 的消費策略是:
1. 每個分區(qū)針對同一個 group 只掛載一個消費者轴咱。
2. 如果同一個 group 的消費者數(shù)目大于分區(qū)數(shù)目既忆,則多出來的消費者將不參與消費。
3. 如果同一個 group 的消費者數(shù)目小于分區(qū)數(shù)目嗦玖,則有部分消費者需要額外承擔消費任務患雇。
在某個消費者故障或者重啟等情況下,其他消費者會感知到這一變化(通過 zookeeper watch 消費者列表)宇挫,然后重新進行負載均衡苛吱,保證所有的分區(qū)都有消費者進行消費。
命名服務(Naming Service)
命名服務也是分布式系統(tǒng)中比較常見的一類場景器瘪。在分布式系統(tǒng)中翠储,通過使用命名服務,客戶端應用能夠根據(jù)指定名字來獲取資源或服務的地址橡疼,提供者等信息援所。被命名的實體通常可以是集群中的機器欣除,提供的服務地址住拭,進程對象等等——這些我們都可以統(tǒng)稱他們?yōu)槊郑∟ame)。其中較為常見的就是一些分布式服務框架中的服務地址列表历帚。通過調(diào)用 ZK 提供的創(chuàng)建節(jié)點的 API滔岳,能夠很容易創(chuàng)建一個全局唯一的 path,這個 path 就可以作為一個名稱挽牢。
阿里開源的分布式服務框架 Dubbo 中使用 ZooKeeper 來作為其命名服務谱煤,維護全局的服務地址列表。在Dubbo 實現(xiàn)中:
? ??服務提供者在啟動的時候禽拔,向 ZK 上的指定節(jié)點/dubbo/${serviceName}/providers 目錄下寫入自己的 URL 地址刘离,這個操作就完成了服務的發(fā)布室叉。
? ??服務消費者啟動的時候,訂閱/dubbo/${serviceName}/providers 目錄下的提供者 URL 地址硫惕, 并向/dubbo/${serviceName} /consumers目錄下寫入自己的 URL 地址太惠。
注意,所有向 ZK 上注冊的地址都是臨時節(jié)點疲憋,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化凿渊。
另外,Dubbo 還有針對服務粒度的監(jiān)控缚柳,方法是訂閱/dubbo/${serviceName}目錄下所有提供者和消費者的信息埃脏。
分布式通知/協(xié)調(diào)
ZooKeeper 中特有 watcher 注冊與異步通知機制,能夠很好的實現(xiàn)分布式環(huán)境下不同系統(tǒng)之間的通知與協(xié)調(diào)秋忙,實現(xiàn)對數(shù)據(jù)變更的實時處理彩掐。使用方法通常是不同系統(tǒng)都對 ZK 上同一個 znode 進行注冊,監(jiān)聽 znode 的變化(包括 znode 本身內(nèi)容及子節(jié)點的)灰追,其中一個系統(tǒng) update 了 znode堵幽,那么另一個系統(tǒng)能夠收到通知,并作出相應處理
另一種心跳檢測機制:檢測系統(tǒng)和被檢測系統(tǒng)之間并不直接關(guān)聯(lián)起來弹澎,而是通過 zk 上某個節(jié)點關(guān)聯(lián)朴下,大大減少系統(tǒng)耦合。
另一種系統(tǒng)調(diào)度模式:某系統(tǒng)有控制臺和推送系統(tǒng)兩部分組成苦蒿,控制臺的職責是控制推送系統(tǒng)進行相應的推送工作殴胧。管理人員在控制臺作的一些操作,實際上是修改了 ZK 上某些節(jié)點的狀態(tài)佩迟,而 ZK 就把這些變化通知給他們注冊 Watcher 的客戶端团滥,即推送系統(tǒng),于是报强,作出相應的推送任務灸姊。
另一種工作匯報模式:一些類似于任務分發(fā)系統(tǒng),子任務啟動后秉溉,到 zk 來注冊一個臨時節(jié)點力惯,并且定時將自己的進度進行匯報(將進度寫回這個臨時節(jié)點),這樣任務管理者就能夠?qū)崟r知道任務進度坚嗜。
總之夯膀,使用 zookeeper 來進行分布式通知和協(xié)調(diào)能夠大大降低系統(tǒng)之間的耦合
集群管理與 Master 選舉
集群機器監(jiān)控:這通常用于那種對集群中機器狀態(tài)诗充,機器在線率有較高要求的場景苍蔬,能夠快速對集群中機器變化作出響應。這樣的場景中蝴蜓,往往有一個監(jiān)控系統(tǒng)碟绑,實時檢測集群機器是否存活俺猿。過去的做法通常是:監(jiān)控系統(tǒng)通過某種手段(比如 ping)定時檢測每個機器,或者每個機器自己定時向監(jiān)控系統(tǒng)匯報“我還活著”格仲。 這種做法可行押袍,但是存在兩個比較明顯的問題:
1. 集群中機器有變動的時候,牽連修改的東西比較多凯肋。
2. 有一定的延時谊惭。
利用 ZooKeeper 有兩個特性,就可以實時另一種集群機器存活性監(jiān)控系統(tǒng):
a. 客戶端在節(jié)點 x 上注冊一個 Watcher侮东,那么如果 x 的子節(jié)點變化了圈盔,會通知該客戶端。
b. 創(chuàng)建 EPHEMERAL 類型的節(jié)點悄雅,一旦客戶端和服務器的會話結(jié)束或過期驱敲,那么該節(jié)點就會消失。
例如宽闲,監(jiān)控系統(tǒng)在 /clusterServers 節(jié)點上注冊一個 Watcher众眨,以后每動態(tài)加機器,那么就往 /clusterServers 下創(chuàng)建一個 EPHEMERAL 類型的節(jié)點:/clusterServers/{hostname}. 這樣容诬,監(jiān)控系統(tǒng)就能夠?qū)崟r知道機器的增減情況娩梨,至于后續(xù)處理就是監(jiān)控系統(tǒng)的業(yè)務了。
Master 選舉則是 zookeeper 中最為經(jīng)典的應用場景
在分布式環(huán)境中览徒,相同的業(yè)務應用分布在不同的機器上姚建,有些業(yè)務邏輯(例如一些耗時的計算,網(wǎng)絡(luò) I/O 處理)吱殉,往往只需要讓整個集群中的某一臺機器進行執(zhí)行掸冤,其余機器可以共享這個結(jié)果,這樣可以大大減少重復勞動友雳,提高性能稿湿,于是這個 master 選舉便是這種場景下的碰到的主要問題。
利用 ZooKeeper 的強一致性押赊,能夠保證在分布式高并發(fā)情況下節(jié)點創(chuàng)建的全局唯一性饺藤,即:同時有多個客戶端請求創(chuàng)建 /currentMaster 節(jié)點,最終一定只有一個客戶端請求能夠創(chuàng)建成功流礁。利用這個特性涕俗,就能很輕易的在分布式環(huán)境中進行集群選取了。
另外神帅,這種場景演化一下再姑,就是動態(tài) Master 選舉。這就要用到 EPHEMERAL_SEQUENTIAL 類型節(jié)點的特性了找御。
上文中提到元镀,所有客戶端創(chuàng)建請求绍填,最終只有一個能夠創(chuàng)建成功。在這里稍微變化下栖疑,就是允許所有請求都能夠創(chuàng)建成功讨永,但是得有個創(chuàng)建順序,于是所有的請求最終在 ZK 上創(chuàng)建結(jié)果的一種可能情況是這樣:/currentMaster/{sessionId}-1 , /currentMaster/{sessionId}-2 , /currentMaster/{sessionId}-3 ….. 每次選取序列號最小的那個機器作為Master遇革,如果這個機器掛了卿闹,由于他創(chuàng)建的節(jié)點會馬上消失,那么之后最小的那個機器就是 Master 了萝快。
在搜索系統(tǒng)中比原,如果集群中每個機器都生成一份全量索引,不僅耗時杠巡,而且不能保證彼此之間索引數(shù)據(jù)一致量窘。因此讓集群中的 Master 來進行全量索引的生成,然后同步到集群中其它機器氢拥。另外蚌铜,Master 選舉的容災措施是,可以隨時進行手動指定 master嫩海,就是說應用在 zk 在無法獲取 master 信息時冬殃,可以通過比如 http 方式,向一個地方獲取 master叁怪。
在 Hbase 中审葬,也是使用 ZooKeeper 來實現(xiàn)動態(tài) HMaster 的選舉。在 Hbase 實現(xiàn)中奕谭,會在 ZK 上存儲一些 ROOT 表的地址和 HMaster 的地址涣觉,HRegionServer 也會把自己以臨時節(jié)點(Ephemeral)的方式注冊到 Zookeeper 中,使得 HMaster 可以隨時感知到各個 HRegionServer的存活狀態(tài)血柳,同時官册,一旦 HMaster 出現(xiàn)問題,會重新選舉出一個 HMaster 來運行难捌,從而避免了 HMaster 的單點問題膝宁。
分布式鎖
分布式鎖,這個主要得益于 ZooKeeper 為我們保證了數(shù)據(jù)的強一致性根吁。鎖服務可以分為兩類员淫,一個是保持獨占,另一個是控制時序击敌。
所謂保持獨占介返,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖愚争。通常的做法是把 zk 上的一個 znode 看作是一把鎖映皆,通過 create znode 的方式來實現(xiàn)挤聘。所有客戶端都去創(chuàng)建 /distribute_lock 節(jié)點轰枝,最終成功創(chuàng)建的那個客戶端也即擁有了這把鎖捅彻。
控制時序,就是所有視圖來獲取這個鎖的客戶端鞍陨,最終都是會被安排執(zhí)行步淹,只是有個全局時序了。做法和上面基本類似诚撵,只是這里/distribute_lock 已 絆 預 先 存 在 缭裆, 客 戶 端 在 它 下 面 創(chuàng) 建 臨 時 有 序 節(jié) 點 ( 這 個 可 以 通 過 節(jié) 點 的 屬 性 控 制 :CreateMode.EPHEMERAL_SEQUENTIAL 來指定)。Zk 的父節(jié)點(/distribute_lock)維持一份 sequence,保證子節(jié)點創(chuàng)建的時序性寿烟,從而也形成了每個客戶端的全局時序澈驼。
分布式隊列
隊列方面,簡單地講有兩種筛武,一種是常規(guī)的先進先出隊列缝其,另一種是要等到隊列成員聚齊之后的才統(tǒng)一按序執(zhí)行。對于第一種先迚先出隊列徘六,和分布式鎖服務中的控制時序場景基本原理一致内边,這里不再贅述。
第二種隊列其實是在 FIFO 隊列的基礎(chǔ)上作了一個增強待锈。通衬洌可以在 /queue 這個 znode 下預先建立一個/queue/num 節(jié)點,并且賦值為 n(或者直接給/queue 賦值 n)竿音,表示隊列大小和屎,之后每次有隊列成員加入后,就判斷下是否已經(jīng)到達隊列大小春瞬,決定是否可以開始執(zhí)行了眶俩。這種用法的典型場景是,分布式環(huán)境中快鱼,一個大任務 Task A颠印,需要在很多子任務完成(或條件就緒)情況下才能進行。這個時候抹竹,凡是其中一個子任務完成(就緒)线罕,那么就去 /taskList 下建立自己的臨時時序節(jié)點(CreateMode.EPHEMERAL_SEQUENTIAL),當 /taskList 發(fā)現(xiàn)自己下面的子節(jié)點滿足指定個數(shù)窃判,就可以進行下一步按序進行處理了钞楼。