ZooKeeper是Hadoop Ecosystem中非常重要的組件怔昨,它的主要功能是為分布式系統(tǒng)提供一致性協(xié)調(diào)(Coordination)服務(wù)铐伴,與之對應(yīng)的Google的類似服務(wù)叫 Chubby英染。今天這篇文章分為三個部分來介紹ZooKeeper轨功,第一部分介紹ZooKeeper的基本原理只嚣,第二部分介紹ZooKeeper提供的 Client API的使用乌询,第三部分介紹一些ZooKeeper典型的應(yīng)用場景魄梯。
ZooKeeper基本原理
1. 數(shù)據(jù)模型
**2.重要概念 **
2.1 ZNode
前文已介紹了ZNode, ZNode根據(jù)其本身的特性,可以分為下面兩類:
Regular ZNode: 常規(guī)型ZNode, 用戶需要顯式的創(chuàng)建顶籽、刪除
Ephemeral ZNode: 臨時型ZNode, 用戶創(chuàng)建它之后玩般,可以顯式的刪除,也可以在創(chuàng)建它的Session結(jié)束后礼饱,由ZooKeeper Server自動刪除
ZNode還有一個Sequential的特性坏为,如果創(chuàng)建的時候指定的話,該ZNode的名字后面會自動Append一個不斷增加的SequenceNo镊绪。
2.2 Session
Client與ZooKeeper之間的通信匀伏,需要創(chuàng)建一個Session,這個Session會有一個超時時間蝴韭。因為ZooKeeper集群會把 Client的Session信息持久化够颠,所以在Session沒超時之前,Client與ZooKeeper Server的連接可以在各個ZooKeeper Server之間透明地移動榄鉴。
在實際的應(yīng)用中履磨,如果Client與Server之間的通信足夠頻繁,Session的維護(hù)就不需要其它額外的消息了庆尘。否則剃诅,ZooKeeper Client會每t/3 ms發(fā)一次心跳給Server,如果Client 2t/3 ms沒收到來自Server的心跳回應(yīng)驶忌,就會換到一個新的ZooKeeper Server上矛辕。這里t是用戶配置的Session的超時時間。
2.3 Watcher
ZooKeeper支持一種Watch操作付魔,Client可以在某個ZNode上設(shè)置一個Watcher聊品,來Watch該ZNode上的變化。如果該 ZNode上有相應(yīng)的變化几苍,就會觸發(fā)這個Watcher杨刨,把相應(yīng)的事件通知給設(shè)置Watcher的Client。需要注意的是擦剑,ZooKeeper中的 Watcher是一次性的妖胀,即觸發(fā)一次就會被取消,如果想繼續(xù)Watch的話惠勒,需要客戶端重新設(shè)置Watcher赚抡。這個跟epoll里的oneshot模式有點類似。
**3. ZooKeeper特性 **
3.1 讀纠屋、寫(更新)模式
在ZooKeeper集群中涂臣,讀可以從任意一個ZooKeeper Server讀,這一點是保證ZooKeeper比較好的讀性能的關(guān)鍵;寫的請求會先Forwarder到Leader赁遗,然后由Leader來通過 ZooKeeper中的原子廣播協(xié)議署辉,將請求廣播給所有的Follower,Leader收到一半以上的寫成功的Ack后岩四,就認(rèn)為該寫成功了哭尝,就會將該寫進(jìn)行持久化,并告訴客戶端寫成功了剖煌。
3.2 WAL和Snapshot
和大多數(shù)分布式系統(tǒng)一樣材鹦,ZooKeeper也有WAL(Write-Ahead-Log),對于每一個更新操作耕姊,ZooKeeper都會先寫WAL, 然后再對內(nèi)存中的數(shù)據(jù)做更新桶唐,然后向Client通知更新結(jié)果。另外茉兰,ZooKeeper還會定期將內(nèi)存中的目錄樹進(jìn)行Snapshot尤泽,落地到磁盤上,這個跟HDFS中的FSImage是比較類似的规脸。這么做的主要目的安吁,一當(dāng)然是數(shù)據(jù)的持久化,二是加快重啟之后的恢復(fù)速度燃辖,如果全部通過Replay WAL的形式恢復(fù)的話鬼店,會比較慢。
3.3 FIFO
對于每一個ZooKeeper客戶端而言黔龟,所有的操作都是遵循FIFO順序的妇智,這一特性是由下面兩個基本特性來保證的:一是ZooKeeper Client與Server之間的網(wǎng)絡(luò)通信是基于TCP,TCP保證了Client/Server之間傳輸包的順序氏身;二是ZooKeeper Server執(zhí)行客戶端請求也是嚴(yán)格按照FIFO順序的巍棱。
3.4 Linearizability
在ZooKeeper中,所有的更新操作都有嚴(yán)格的偏序關(guān)系蛋欣,更新操作都是串行執(zhí)行的航徙,這一點是保證ZooKeeper功能正確性的關(guān)鍵。
3.5 ZooKeeper Client API
ZooKeeper Client Library提供了豐富直觀的API供用戶程序使用陷虎,下面是一些常用的API:
create(path, data, flags): 創(chuàng)建一個ZNode, path是其路徑到踏,data是要存儲在該ZNode上的數(shù)據(jù),flags常用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
delete(path, version): 刪除一個ZNode尚猿,可以通過version刪除指定的版本, 如果version是-1的話窝稿,表示刪除所有的版本.exists(path, watch): 判斷指定ZNode是否存在,并設(shè)置是否Watch這個ZNode凿掂。這里如果要設(shè)置Watcher的話伴榔,Watcher是在創(chuàng)建ZooKeeper實例時指定的,如果要設(shè)置特定的Watcher的話,可以調(diào)用另一個重載版本的exists(path, watcher)踪少。以下幾個帶watch參數(shù)的API也都類似
getData(path, watch): 讀取指定ZNode上的數(shù)據(jù)塘安,并設(shè)置是否watch這個ZNode
setData(path, watch): 更新指定ZNode的數(shù)據(jù),并設(shè)置是否Watch這個ZNode
getChildren(path, watch): 獲取指定ZNode的所有子ZNode的名字援奢,并設(shè)置是否Watch這個ZNode兼犯。sync(path): 把所有在sync之前的更新操作都進(jìn)行同步,達(dá)到每個請求都在半數(shù)以上的ZooKeeper Server上生效萝究。path參數(shù)目前沒有用
setAcl(path, acl): 設(shè)置指定ZNode的Acl信息
getAcl(path): 獲取指定ZNode的Acl信息
ZooKeeper典型應(yīng)用場景
**1. 名字服務(wù)(NameService) ** 分布式應(yīng)用中,通常需要一套完備的命令機(jī)制锉罐,既能產(chǎn)生唯一的標(biāo)識帆竹,又方便人識別和記憶。 我們知道脓规,每個ZNode都可以由其路徑唯一標(biāo)識栽连,路徑本身也比較簡潔直觀,另外ZNode上還可以存儲少量數(shù)據(jù)侨舆,這些都是實現(xiàn)統(tǒng)一的 NameService的基礎(chǔ)秒紧。下面以在HDFS中實現(xiàn)NameService為例,來說明實現(xiàn)NameService的基本布驟:
目標(biāo):通過簡單的名字來訪問指定的HDFS機(jī)群
定義命名規(guī)則:這里要做到簡潔易記憶挨下。下面是一種可選的方案: [serviceScheme://][zkCluster]-[clusterName]熔恢,比如hdfs://lgprc-example/表示基于 lgprc ZooKeeper集群的用來做example的HDFS集群
配置DNS映射: 將zkCluster的標(biāo)識lgprc通過DNS解析到對應(yīng)的ZooKeeper集群的地址
創(chuàng)建ZNode: 在對應(yīng)的ZooKeeper上創(chuàng)建/NameService/hdfs/lgprc-example結(jié)點,將HDFS的配置文件存儲于該結(jié)點下
用戶程序要訪問hdfs://lgprc-example/的HDFS集群臭笆,首先通過DNS找到lgprc的ZooKeeper機(jī)群的地址叙淌,然后在 ZooKeeper的/NameService/hdfs/lgprc-example結(jié)點中讀取到HDFS的配置,進(jìn)而根據(jù)得到的配置愁铺,得到HDFS的實際訪問入口
**2. 配置管理(Configuration Management) ** 在分布式系統(tǒng)中鹰霍,常會遇到這樣的場景: 某個Job的很多個實例在運(yùn)行,它們在運(yùn)行時大多數(shù)配置項是相同的茵乱,如果想要統(tǒng)一改某個配置茂洒,一個個實例去改,是比較低效瓶竭,也是比較容易出錯的方式督勺。通過ZooKeeper可以很好的解決這樣的問題,下面的基本的步驟:
將公共的配置內(nèi)容放到ZooKeeper中某個ZNode上斤贰,比如/service/common-conf
所有的實例在啟動時都會傳入ZooKeeper集群的入口地址玷氏,并且在運(yùn)行過程中Watch /service/common-conf這個ZNode
如果集群管理員修改了了common-conf,所有的實例都會被通知到腋舌,根據(jù)收到的通知更新自己的配置盏触,并繼續(xù)Watch /service/common-conf
**3. 組員管理(Group Membership) ** 在典型的Master-Slave結(jié)構(gòu)的分布式系統(tǒng)中,Master需要作為“總管”來管理所有的Slave, 當(dāng)有Slave加入,或者有Slave宕機(jī)赞辩,Master都需要感知到這個事情雌芽,然后作出對應(yīng)的調(diào)整,以便不影響整個集群對外提供服務(wù)辨嗽。以HBase為例世落,HMaster管理了所有的RegionServer,當(dāng)有新的RegionServer加入的時候糟需,HMaster需要分配一些Region到該 RegionServer上去屉佳,讓其提供服務(wù);當(dāng)有RegionServer宕機(jī)時洲押,HMaster需要將該RegionServer之前服務(wù)的 Region都重新分配到當(dāng)前正在提供服務(wù)的其它RegionServer上武花,以便不影響客戶端的正常訪問。下面是這種場景下使用ZooKeeper的基本步驟:
Master在ZooKeeper上創(chuàng)建/service/slaves結(jié)點杈帐,并設(shè)置對該結(jié)點的Watcher
每個Slave在啟動成功后体箕,創(chuàng)建唯一標(biāo)識自己的臨時性(Ephemeral)結(jié)點/service/slaves/${slave_id},并將自己地址(ip/port)等相關(guān)信息寫入該結(jié)點
Master收到有新子結(jié)點加入的通知后挑童,做相應(yīng)的處理
如果有Slave宕機(jī)累铅,由于它所對應(yīng)的結(jié)點是臨時性結(jié)點,在它的Session超時后站叼,ZooKeeper會自動刪除該結(jié)點
Master收到有子結(jié)點消失的通知娃兽,做相應(yīng)的處理
**4. 簡單互斥鎖(Simple Lock) ** 我們知識,在傳統(tǒng)的應(yīng)用程序中尽楔,線程换薄、進(jìn)程的同步,都可以通過操作系統(tǒng)提供的機(jī)制來完成翔试。但是在分布式系統(tǒng)中轻要,多個進(jìn)程之間的同步,操作系統(tǒng)層面就無能為力了垦缅。這時候就需要像ZooKeeper這樣的分布式的協(xié)調(diào)(Coordination)服務(wù)來協(xié)助完成同步冲泥,下面是用ZooKeeper實現(xiàn)簡單的互斥鎖的步驟,這個可以和線程間同步的mutex做類比來理解:
多個進(jìn)程嘗試去在指定的目錄下去創(chuàng)建一個臨時性(Ephemeral)結(jié)點 /locks/my_lock
ZooKeeper能保證壁涎,只會有一個進(jìn)程成功創(chuàng)建該結(jié)點凡恍,創(chuàng)建結(jié)點成功的進(jìn)程就是搶到鎖的進(jìn)程,假設(shè)該進(jìn)程為A
其它進(jìn)程都對/locks/my_lock進(jìn)行Watch
當(dāng) A進(jìn)程不再需要鎖怔球,可以顯式刪除/locks/my_lock釋放鎖嚼酝;或者是A進(jìn)程宕機(jī)后Session超時,ZooKeeper系統(tǒng)自動刪除 /locks/my_lock結(jié)點釋放鎖竟坛。此時闽巩,其它進(jìn)程就會收到ZooKeeper的通知钧舌,并嘗試去創(chuàng)建/locks/my_lock搶鎖,如此循環(huán)反復(fù)
**5. 互斥鎖(Simple Lock without Herd Effect) ** 上一節(jié)的例子中有一個問題涎跨,每次搶鎖都會有大量的進(jìn)程去競爭洼冻,會造成羊群效應(yīng)(Herd Effect),為了解決這個問題隅很,我們可以通過下面的步驟來改進(jìn)上述過程:
每個進(jìn)程都在ZooKeeper上創(chuàng)建一個臨時的順序結(jié)點(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的為當(dāng)前的持鎖者(${seq}是ZooKeeper生成的Sequenctial Number)
其它進(jìn)程都對只watch比它次小的進(jìn)程對應(yīng)的結(jié)點撞牢,比如2 watch 1, 3 watch 2, 以此類推
當(dāng)前持鎖者釋放鎖后,比它次大的進(jìn)程就會收到ZooKeeper的通知叔营,它成為新的持鎖者屋彪,如此循環(huán)反復(fù)
這里需要補(bǔ)充一點,通常在分布式系統(tǒng)中用ZooKeeper來做Leader Election(選主)就是通過上面的機(jī)制來實現(xiàn)的绒尊,這里的持鎖者就是當(dāng)前的“主”畜挥。
**6. 讀寫鎖(Read/Write Lock) ** 我們知道,讀寫鎖跟互斥鎖相比不同的地方是垒酬,它分成了讀和寫兩種模式砰嘁,多個讀可以并發(fā)執(zhí)行件炉,但寫和讀勘究、寫都互斥,不能同時執(zhí)行行斟冕。利用ZooKeeper口糕,在上面的基礎(chǔ)上,稍做修改也可以實現(xiàn)傳統(tǒng)的讀寫鎖的語義磕蛇,下面是基本的步驟:
每個進(jìn)程都在ZooKeeper上創(chuàng)建一個臨時的順序結(jié)點(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的一個或多個結(jié)點為當(dāng)前的持鎖者景描,多個是因為多個讀可以并發(fā)
需要寫鎖的進(jìn)程,Watch比它次小的進(jìn)程對應(yīng)的結(jié)點
需要讀鎖的進(jìn)程秀撇,Watch比它小的最后一個寫進(jìn)程對應(yīng)的結(jié)點
當(dāng)前結(jié)點釋放鎖后超棺,所有Watch該結(jié)點的進(jìn)程都會被通知到,他們成為新的持鎖者呵燕,如此循環(huán)反復(fù)
**7. 屏障(Barrier) ** 在分布式系統(tǒng)中棠绘,屏障是這樣一種語義: 客戶端需要等待多個進(jìn)程完成各自的任務(wù),然后才能繼續(xù)往前進(jìn)行下一步再扭。下用是用ZooKeeper來實現(xiàn)屏障的基本步驟:
Client在ZooKeeper上創(chuàng)建屏障結(jié)點/barrier/my_barrier氧苍,并啟動執(zhí)行各個任務(wù)的進(jìn)程
Client通過exist()來Watch /barrier/my_barrier結(jié)點
每個任務(wù)進(jìn)程在完成任務(wù)后,去檢查是否達(dá)到指定的條件泛范,如果沒達(dá)到就啥也不做让虐,如果達(dá)到了就把/barrier/my_barrier結(jié)點刪除
Client收到/barrier/my_barrier被刪除的通知,屏障消失罢荡,繼續(xù)下一步任務(wù)
8. 雙屏障(Double Barrier) 雙屏障是這樣一種語義: 它可以用來同步一個任務(wù)的開始和結(jié)束赡突,當(dāng)有足夠多的進(jìn)程進(jìn)入屏障后对扶,才開始執(zhí)行任務(wù);當(dāng)所有的進(jìn)程都執(zhí)行完各自的任務(wù)后麸俘,屏障才撤銷辩稽。下面是用ZooKeeper來實現(xiàn)雙屏障的基本步驟:
進(jìn)入屏障:Client Watch /barrier/ready結(jié)點, 通過判斷該結(jié)點是否存在來決定是否啟動任務(wù)
每個任務(wù)進(jìn)程進(jìn)入屏障時創(chuàng)建一個臨時結(jié)點/barrier/process/${process_id},然后檢查進(jìn)入屏障的結(jié)點數(shù)是否達(dá)到指定的值从媚,如果達(dá)到了指定的值逞泄,就創(chuàng)建一個/barrier/ready結(jié)點,否則繼續(xù)等待
Client收到/barrier/ready創(chuàng)建的通知拜效,就啟動任務(wù)執(zhí)行過程
離開屏障:Client Watch /barrier/process喷众,如果其沒有子結(jié)點,就可以認(rèn)為任務(wù)執(zhí)行結(jié)束紧憾,可以離開屏障
每個任務(wù)進(jìn)程執(zhí)行任務(wù)結(jié)束后到千,都需要刪除自己對應(yīng)的結(jié)點/barrier/process/${process_id}
原文鏈接:http://www.wuzesheng.com/?p=2609
比較好的一篇文章 :zookeeper