ZooKeeper是Hadoop Ecosystem中非常重要的組件,它的主要功能是為分布式系統(tǒng)提供一致性協(xié)調(diào)(Coordination)服務(wù),與之對(duì)應(yīng)的Google的類似服務(wù)叫Chubby。今天這篇文章分為三個(gè)部分來介紹ZooKeeper,第一部分介紹ZooKeeper的基本原理,第二部分介紹ZooKeeper提供的Client API的使用,第三部分介紹一些ZooKeeper典型的應(yīng)用場(chǎng)景。
1. 數(shù)據(jù)模型

如上圖所示,ZooKeeper數(shù)據(jù)模型的結(jié)構(gòu)與Unix文件系統(tǒng)很類似,整體上可以看作是一棵樹,每個(gè)節(jié)點(diǎn)稱做一個(gè)ZNode。每個(gè)ZNode都可以通過其路徑唯一標(biāo)識(shí),比如上圖中第三層的第一個(gè)ZNode, 它的路徑是/app1/c1。在每個(gè)ZNode上可存儲(chǔ)少量數(shù)據(jù)(默認(rèn)是1M, 可以通過配置修改, 通常不建議在ZNode上存儲(chǔ)大量的數(shù)據(jù)),這個(gè)特性非常有用,在后面的典型應(yīng)用場(chǎng)景中會(huì)介紹到。另外,每個(gè)ZNode上還存儲(chǔ)了其Acl信息,這里需要注意,雖說ZNode的樹形結(jié)構(gòu)跟Unix文件系統(tǒng)很類似,但是其Acl與Unix文件系統(tǒng)是完全不同的,每個(gè)ZNode的Acl的獨(dú)立的,子結(jié)點(diǎn)不會(huì)繼承父結(jié)點(diǎn)的,關(guān)于ZooKeeper中的Acl可以參考之前寫過的一篇文章《說說Zookeeper中的ACL》。
2.重要概念
2.1 ZNode
前文已介紹了ZNode, ZNode根據(jù)其本身的特性,可以分為下面兩類:
Regular ZNode: 常規(guī)型ZNode, 用戶需要顯式的創(chuàng)建、刪除
Ephemeral ZNode: 臨時(shí)型ZNode, 用戶創(chuàng)建它之后,可以顯式的刪除,也可以在創(chuàng)建它的Session結(jié)束后,由ZooKeeper Server自動(dòng)刪除
ZNode還有一個(gè)Sequential的特性,如果創(chuàng)建的時(shí)候指定的話,該ZNode的名字后面會(huì)自動(dòng)Append一個(gè)不斷增加的SequenceNo。
2.2 Session
Client與ZooKeeper之間的通信,需要?jiǎng)?chuàng)建一個(gè)Session,這個(gè)Session會(huì)有一個(gè)超時(shí)時(shí)間。因?yàn)閆ooKeeper集群會(huì)把Client的Session信息持久化,所以在Session沒超時(shí)之前,Client與ZooKeeper Server的連接可以在各個(gè)ZooKeeper Server之間透明地移動(dòng)。
在實(shí)際的應(yīng)用中,如果Client與Server之間的通信足夠頻繁,Session的維護(hù)就不需要其它額外的消息了。否則,ZooKeeper Client會(huì)每t/3 ms發(fā)一次心跳給Server,如果Client 2t/3 ms沒收到來自Server的心跳回應(yīng),就會(huì)換到一個(gè)新的ZooKeeper Server上。這里t是用戶配置的Session的超時(shí)時(shí)間。
2.3 Watcher
ZooKeeper支持一種Watch操作,Client可以在某個(gè)ZNode上設(shè)置一個(gè)Watcher,來Watch該ZNode上的變化。如果該ZNode上有相應(yīng)的變化,就會(huì)觸發(fā)這個(gè)Watcher,把相應(yīng)的事件通知給設(shè)置Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即觸發(fā)一次就會(huì)被取消,如果想繼續(xù)Watch的話,需要客戶端重新設(shè)置Watcher。這個(gè)跟epoll里的oneshot模式有點(diǎn)類似。
3. ZooKeeper特性
3.1 讀、寫(更新)模式
在ZooKeeper集群中,讀可以從任意一個(gè)ZooKeeper Server讀,這一點(diǎn)是保證ZooKeeper比較好的讀性能的關(guān)鍵;寫的請(qǐng)求會(huì)先Forwarder到Leader,然后由Leader來通過ZooKeeper中的原子廣播協(xié)議,將請(qǐng)求廣播給所有的Follower,Leader收到一半以上的寫成功的Ack后,就認(rèn)為該寫成功了,就會(huì)將該寫進(jìn)行持久化,并告訴客戶端寫成功了。
3.2 WAL和Snapshot
和大多數(shù)分布式系統(tǒng)一樣,ZooKeeper也有WAL(Write-Ahead-Log),對(duì)于每一個(gè)更新操作,ZooKeeper都會(huì)先寫WAL, 然后再對(duì)內(nèi)存中的數(shù)據(jù)做更新,然后向Client通知更新結(jié)果。另外,ZooKeeper還會(huì)定期將內(nèi)存中的目錄樹進(jìn)行Snapshot,落地到磁盤上,這個(gè)跟HDFS中的FSImage是比較類似的。這么做的主要目的,一當(dāng)然是數(shù)據(jù)的持久化,二是加快重啟之后的恢復(fù)速度,如果全部通過Replay WAL的形式恢復(fù)的話,會(huì)比較慢。
3.3 FIFO
對(duì)于每一個(gè)ZooKeeper客戶端而言,所有的操作都是遵循FIFO順序的,這一特性是由下面兩個(gè)基本特性來保證的:一是ZooKeeper Client與Server之間的網(wǎng)絡(luò)通信是基于TCP,TCP保證了Client/Server之間傳輸包的順序;二是ZooKeeper Server執(zhí)行客戶端請(qǐng)求也是嚴(yán)格按照FIFO順序的。
3.4 Linearizability
在ZooKeeper中,所有的更新操作都有嚴(yán)格的偏序關(guān)系,更新操作都是串行執(zhí)行的,這一點(diǎn)是保證ZooKeeper功能正確性的關(guān)鍵。
ZooKeeper Client Library提供了豐富直觀的API供用戶程序使用,下面是一些常用的API:
create(path, data, flags): 創(chuàng)建一個(gè)ZNode, path是其路徑,data是要存儲(chǔ)在該ZNode上的數(shù)據(jù),flags常用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
delete(path, version): 刪除一個(gè)ZNode,可以通過version刪除指定的版本, 如果version是-1的話,表示刪除所有的版本
exists(path, watch): 判斷指定ZNode是否存在,并設(shè)置是否Watch這個(gè)ZNode。這里如果要設(shè)置Watcher的話,Watcher是在創(chuàng)建ZooKeeper實(shí)例時(shí)指定的,如果要設(shè)置特定的Watcher的話,可以調(diào)用另一個(gè)重載版本的exists(path, watcher)。以下幾個(gè)帶watch參數(shù)的API也都類似
getData(path, watch): 讀取指定ZNode上的數(shù)據(jù),并設(shè)置是否watch這個(gè)ZNode
setData(path, watch): 更新指定ZNode的數(shù)據(jù),并設(shè)置是否Watch這個(gè)ZNode
getChildren(path, watch): 獲取指定ZNode的所有子ZNode的名字,并設(shè)置是否Watch這個(gè)ZNode
sync(path): 把所有在sync之前的更新操作都進(jìn)行同步,達(dá)到每個(gè)請(qǐng)求都在半數(shù)以上的ZooKeeper Server上生效。path參數(shù)目前沒有用
setAcl(path, acl): 設(shè)置指定ZNode的Acl信息
getAcl(path): 獲取指定ZNode的Acl信息
1. 名字服務(wù)(NameService)
分布式應(yīng)用中,通常需要一套完備的命令機(jī)制,既能產(chǎn)生唯一的標(biāo)識(shí),又方便人識(shí)別和記憶。 我們知道,每個(gè)ZNode都可以由其路徑唯一標(biāo)識(shí),路徑本身也比較簡(jiǎn)潔直觀,另外ZNode上還可以存儲(chǔ)少量數(shù)據(jù),這些都是實(shí)現(xiàn)統(tǒng)一的NameService的基礎(chǔ)。下面以在HDFS中實(shí)現(xiàn)NameService為例,來說明實(shí)現(xiàn)NameService的基本布驟:
目標(biāo):通過簡(jiǎn)單的名字來訪問指定的HDFS機(jī)群
定義命名規(guī)則:這里要做到簡(jiǎn)潔易記憶。下面是一種可選的方案: [serviceScheme://][zkCluster]-[clusterName],比如hdfs://lgprc-example/表示基于lgprc ZooKeeper集群的用來做example的HDFS集群
配置DNS映射: 將zkCluster的標(biāo)識(shí)lgprc通過DNS解析到對(duì)應(yīng)的ZooKeeper集群的地址
創(chuàng)建ZNode: 在對(duì)應(yīng)的ZooKeeper上創(chuàng)建/NameService/hdfs/lgprc-example結(jié)點(diǎn),將HDFS的配置文件存儲(chǔ)于該結(jié)點(diǎn)下
用戶程序要訪問hdfs://lgprc-example/的HDFS集群,首先通過DNS找到lgprc的ZooKeeper機(jī)群的地址,然后在ZooKeeper的/NameService/hdfs/lgprc-example結(jié)點(diǎn)中讀取到HDFS的配置,進(jìn)而根據(jù)得到的配置,得到HDFS的實(shí)際訪問入口
2. 配置管理(Configuration Management)
在分布式系統(tǒng)中,常會(huì)遇到這樣的場(chǎng)景: 某個(gè)Job的很多個(gè)實(shí)例在運(yùn)行,它們?cè)谶\(yùn)行時(shí)大多數(shù)配置項(xiàng)是相同的,如果想要統(tǒng)一改某個(gè)配置,一個(gè)個(gè)實(shí)例去改,是比較低效,也是比較容易出錯(cuò)的方式。通過ZooKeeper可以很好的解決這樣的問題,下面的基本的步驟:
將公共的配置內(nèi)容放到ZooKeeper中某個(gè)ZNode上,比如/service/common-conf
所有的實(shí)例在啟動(dòng)時(shí)都會(huì)傳入ZooKeeper集群的入口地址,并且在運(yùn)行過程中Watch /service/common-conf這個(gè)ZNode
如果集群管理員修改了了common-conf,所有的實(shí)例都會(huì)被通知到,根據(jù)收到的通知更新自己的配置,并繼續(xù)Watch /service/common-conf
3. 組員管理(Group Membership)
在典型的Master-Slave結(jié)構(gòu)的分布式系統(tǒng)中,Master需要作為“總管”來管理所有的Slave, 當(dāng)有Slave加入,或者有Slave宕機(jī),Master都需要感知到這個(gè)事情,然后作出對(duì)應(yīng)的調(diào)整,以便不影響整個(gè)集群對(duì)外提供服務(wù)。以HBase為例,HMaster管理了所有的RegionServer,當(dāng)有新的RegionServer加入的時(shí)候,HMaster需要分配一些Region到該RegionServer上去,讓其提供服務(wù);當(dāng)有RegionServer宕機(jī)時(shí),HMaster需要將該RegionServer之前服務(wù)的Region都重新分配到當(dāng)前正在提供服務(wù)的其它RegionServer上,以便不影響客戶端的正常訪問。下面是這種場(chǎng)景下使用ZooKeeper的基本步驟:
Master在ZooKeeper上創(chuàng)建/service/slaves結(jié)點(diǎn),并設(shè)置對(duì)該結(jié)點(diǎn)的Watcher
每個(gè)Slave在啟動(dòng)成功后,創(chuàng)建唯一標(biāo)識(shí)自己的臨時(shí)性(Ephemeral)結(jié)點(diǎn)/service/slaves/${slave_id},并將自己地址(ip/port)等相關(guān)信息寫入該結(jié)點(diǎn)
Master收到有新子結(jié)點(diǎn)加入的通知后,做相應(yīng)的處理
如果有Slave宕機(jī),由于它所對(duì)應(yīng)的結(jié)點(diǎn)是臨時(shí)性結(jié)點(diǎn),在它的Session超時(shí)后,ZooKeeper會(huì)自動(dòng)刪除該結(jié)點(diǎn)
Master收到有子結(jié)點(diǎn)消失的通知,做相應(yīng)的處理
4. 簡(jiǎn)單互斥鎖(Simple Lock)
我們知識(shí),在傳統(tǒng)的應(yīng)用程序中,線程、進(jìn)程的同步,都可以通過操作系統(tǒng)提供的機(jī)制來完成。但是在分布式系統(tǒng)中,多個(gè)進(jìn)程之間的同步,操作系統(tǒng)層面就無能為力了。這時(shí)候就需要像ZooKeeper這樣的分布式的協(xié)調(diào)(Coordination)服務(wù)來協(xié)助完成同步,下面是用ZooKeeper實(shí)現(xiàn)簡(jiǎn)單的互斥鎖的步驟,這個(gè)可以和線程間同步的mutex做類比來理解:
多個(gè)進(jìn)程嘗試去在指定的目錄下去創(chuàng)建一個(gè)臨時(shí)性(Ephemeral)結(jié)點(diǎn) /locks/my_lock
ZooKeeper能保證,只會(huì)有一個(gè)進(jìn)程成功創(chuàng)建該結(jié)點(diǎn),創(chuàng)建結(jié)點(diǎn)成功的進(jìn)程就是搶到鎖的進(jìn)程,假設(shè)該進(jìn)程為A
其它進(jìn)程都對(duì)/locks/my_lock進(jìn)行Watch
當(dāng)A進(jìn)程不再需要鎖,可以顯式刪除/locks/my_lock釋放鎖;或者是A進(jìn)程宕機(jī)后Session超時(shí),ZooKeeper系統(tǒng)自動(dòng)刪除/locks/my_lock結(jié)點(diǎn)釋放鎖。此時(shí),其它進(jìn)程就會(huì)收到ZooKeeper的通知,并嘗試去創(chuàng)建/locks/my_lock搶鎖,如此循環(huán)反復(fù)
5. 互斥鎖(Simple Lock without Herd Effect)
上一節(jié)的例子中有一個(gè)問題,每次搶鎖都會(huì)有大量的進(jìn)程去競(jìng)爭(zhēng),會(huì)造成羊群效應(yīng)(Herd Effect),為了解決這個(gè)問題,我們可以通過下面的步驟來改進(jìn)上述過程:
每個(gè)進(jìn)程都在ZooKeeper上創(chuàng)建一個(gè)臨時(shí)的順序結(jié)點(diǎn)(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的為當(dāng)前的持鎖者(${seq}是ZooKeeper生成的Sequenctial Number)
其它進(jìn)程都對(duì)只watch比它次小的進(jìn)程對(duì)應(yīng)的結(jié)點(diǎn),比如2 watch 1, 3 watch 2, 以此類推
當(dāng)前持鎖者釋放鎖后,比它次大的進(jìn)程就會(huì)收到ZooKeeper的通知,它成為新的持鎖者,如此循環(huán)反復(fù)
這里需要補(bǔ)充一點(diǎn),通常在分布式系統(tǒng)中用ZooKeeper來做Leader Election(選主)就是通過上面的機(jī)制來實(shí)現(xiàn)的,這里的持鎖者就是當(dāng)前的“主”。
6. 讀寫鎖(Read/Write Lock)
我們知道,讀寫鎖跟互斥鎖相比不同的地方是,它分成了讀和寫兩種模式,多個(gè)讀可以并發(fā)執(zhí)行,但寫和讀、寫都互斥,不能同時(shí)執(zhí)行行。利用ZooKeeper,在上面的基礎(chǔ)上,稍做修改也可以實(shí)現(xiàn)傳統(tǒng)的讀寫鎖的語義,下面是基本的步驟:
每個(gè)進(jìn)程都在ZooKeeper上創(chuàng)建一個(gè)臨時(shí)的順序結(jié)點(diǎn)(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的一個(gè)或多個(gè)結(jié)點(diǎn)為當(dāng)前的持鎖者,多個(gè)是因?yàn)槎鄠€(gè)讀可以并發(fā)
需要寫鎖的進(jìn)程,Watch比它次小的進(jìn)程對(duì)應(yīng)的結(jié)點(diǎn)
需要讀鎖的進(jìn)程,Watch比它小的最后一個(gè)寫進(jìn)程對(duì)應(yīng)的結(jié)點(diǎn)
當(dāng)前結(jié)點(diǎn)釋放鎖后,所有Watch該結(jié)點(diǎn)的進(jìn)程都會(huì)被通知到,他們成為新的持鎖者,如此循環(huán)反復(fù)
7. 屏障(Barrier)
在分布式系統(tǒng)中,屏障是這樣一種語義: 客戶端需要等待多個(gè)進(jìn)程完成各自的任務(wù),然后才能繼續(xù)往前進(jìn)行下一步。下用是用ZooKeeper來實(shí)現(xiàn)屏障的基本步驟:
Client在ZooKeeper上創(chuàng)建屏障結(jié)點(diǎn)/barrier/my_barrier,并啟動(dòng)執(zhí)行各個(gè)任務(wù)的進(jìn)程
Client通過exist()來Watch /barrier/my_barrier結(jié)點(diǎn)
每個(gè)任務(wù)進(jìn)程在完成任務(wù)后,去檢查是否達(dá)到指定的條件,如果沒達(dá)到就啥也不做,如果達(dá)到了就把/barrier/my_barrier結(jié)點(diǎn)刪除
Client收到/barrier/my_barrier被刪除的通知,屏障消失,繼續(xù)下一步任務(wù)
8. 雙屏障(Double Barrier)
雙屏障是這樣一種語義: 它可以用來同步一個(gè)任務(wù)的開始和結(jié)束,當(dāng)有足夠多的進(jìn)程進(jìn)入屏障后,才開始執(zhí)行任務(wù);當(dāng)所有的進(jìn)程都執(zhí)行完各自的任務(wù)后,屏障才撤銷。下面是用ZooKeeper來實(shí)現(xiàn)雙屏障的基本步驟:
進(jìn)入屏障:
Client Watch /barrier/ready結(jié)點(diǎn), 通過判斷該結(jié)點(diǎn)是否存在來決定是否啟動(dòng)任務(wù)
每個(gè)任務(wù)進(jìn)程進(jìn)入屏障時(shí)創(chuàng)建一個(gè)臨時(shí)結(jié)點(diǎn)/barrier/process/${process_id},然后檢查進(jìn)入屏障的結(jié)點(diǎn)數(shù)是否達(dá)到指定的值,如果達(dá)到了指定的值,就創(chuàng)建一個(gè)/barrier/ready結(jié)點(diǎn),否則繼續(xù)等待
Client收到/barrier/ready創(chuàng)建的通知,就啟動(dòng)任務(wù)執(zhí)行過程
離開屏障:
Client Watch /barrier/process,如果其沒有子結(jié)點(diǎn),就可以認(rèn)為任務(wù)執(zhí)行結(jié)束,可以離開屏障
每個(gè)任務(wù)進(jìn)程執(zhí)行任務(wù)結(jié)束后,都需要?jiǎng)h除自己對(duì)應(yīng)的結(jié)點(diǎn)/barrier/process/${process_id}
轉(zhuǎn)載地址:http://www.wuzesheng.com/?p=2609
數(shù)據(jù)發(fā)布與訂閱(配置中心)
發(fā)布與訂閱模型,即所謂的配置中心,顧名思義就是發(fā)布者將數(shù)據(jù)發(fā)布到ZK節(jié)點(diǎn)上,供訂閱者動(dòng)態(tài)獲取數(shù)據(jù),實(shí)現(xiàn)配置信息的集中式管理和動(dòng)態(tài)更新。例如全局的配置信息,服務(wù)式服務(wù)框架的服務(wù)地址列表等就非常適合使用。
應(yīng)用中用到的一些配置信息放到ZK上進(jìn)行集中管理。這類場(chǎng)景通常是這樣:應(yīng)用在啟動(dòng)的時(shí)候會(huì)主動(dòng)來獲取一次配置,同時(shí),在節(jié)點(diǎn)上注冊(cè)一個(gè)Watcher,這樣一來,以后每次配置有更新的時(shí)候,都會(huì)實(shí)時(shí)通知到訂閱的客戶端,從來達(dá)到獲取最新配置信息的目的。
分布式搜索服務(wù)中,索引的元信息和服務(wù)器集群機(jī)器的節(jié)點(diǎn)狀態(tài)存放在ZK的一些指定節(jié)點(diǎn),供各個(gè)客戶端訂閱使用。
分布式日志收集系統(tǒng)。這個(gè)系統(tǒng)的核心工作是收集分布在不同機(jī)器的日志。收集器通常是按照應(yīng)用來分配收集任務(wù)單元,因此需要在ZK上創(chuàng)建一個(gè)以應(yīng)用名作為path的節(jié)點(diǎn)P,并將這個(gè)應(yīng)用的所有機(jī)器ip,以子節(jié)點(diǎn)的形式注冊(cè)到節(jié)點(diǎn)P上,這樣一來就能夠?qū)崿F(xiàn)機(jī)器變動(dòng)的時(shí)候,能夠?qū)崟r(shí)通知到收集器調(diào)整任務(wù)分配。
系統(tǒng)中有些信息需要?jiǎng)討B(tài)獲取,并且還會(huì)存在人工手動(dòng)去修改這個(gè)信息的發(fā)問。通常是暴露出接口,例如JMX接口,來獲取一些運(yùn)行時(shí)的信息。引入ZK之后,就不用自己實(shí)現(xiàn)一套方案了,只要將這些信息存放到指定的ZK節(jié)點(diǎn)上即可。
注意:在上面提到的應(yīng)用場(chǎng)景中,有個(gè)默認(rèn)前提是:數(shù)據(jù)量很小,但是數(shù)據(jù)更新可能會(huì)比較快的場(chǎng)景。
負(fù)載均衡
這里說的負(fù)載均衡是指軟負(fù)載均衡。在分布式環(huán)境中,為了保證高可用性,通常同一個(gè)應(yīng)用或同一個(gè)服務(wù)的提供方都會(huì)部署多份,達(dá)到對(duì)等服務(wù)。而消費(fèi)者就須要在這些對(duì)等的服務(wù)器中選擇一個(gè)來執(zhí)行相關(guān)的業(yè)務(wù)邏輯,其中比較典型的是消息中間件中的生產(chǎn)者,消費(fèi)者負(fù)載均衡。
消息中間件中發(fā)布者和訂閱者的負(fù)載均衡,linkedin開源的KafkaMQ和阿里開源的metaq都是通過zookeeper來做到生產(chǎn)者、消費(fèi)者的負(fù)載均衡。這里以metaq為例如講下:
生產(chǎn)者負(fù)載均衡:metaq發(fā)送消息的時(shí)候,生產(chǎn)者在發(fā)送消息的時(shí)候必須選擇一臺(tái)broker上的一個(gè)分區(qū)來發(fā)送消息,因此metaq在運(yùn)行過程中,會(huì)把所有broker和對(duì)應(yīng)的分區(qū)信息全部注冊(cè)到ZK指定節(jié)點(diǎn)上,默認(rèn)的策略是一個(gè)依次輪詢的過程,生產(chǎn)者在通過ZK獲取分區(qū)列表之后,會(huì)按照brokerId和partition的順序排列組織成一個(gè)有序的分區(qū)列表,發(fā)送的時(shí)候按照從頭到尾循環(huán)往復(fù)的方式選擇一個(gè)分區(qū)來發(fā)送消息。
消費(fèi)負(fù)載均衡:
在消費(fèi)過程中,一個(gè)消費(fèi)者會(huì)消費(fèi)一個(gè)或多個(gè)分區(qū)中的消息,但是一個(gè)分區(qū)只會(huì)由一個(gè)消費(fèi)者來消費(fèi)。MetaQ的消費(fèi)策略是:
每個(gè)分區(qū)針對(duì)同一個(gè)group只掛載一個(gè)消費(fèi)者。
如果同一個(gè)group的消費(fèi)者數(shù)目大于分區(qū)數(shù)目,則多出來的消費(fèi)者將不參與消費(fèi)。
如果同一個(gè)group的消費(fèi)者數(shù)目小于分區(qū)數(shù)目,則有部分消費(fèi)者需要額外承擔(dān)消費(fèi)任務(wù)。
在某個(gè)消費(fèi)者故障或者重啟等情況下,其他消費(fèi)者會(huì)感知到這一變化(通過 zookeeper watch消費(fèi)者列表),然后重新進(jìn)行負(fù)載均衡,保證所有的分區(qū)都有消費(fèi)者進(jìn)行消費(fèi)。
命名服務(wù)(Naming Service)
命名服務(wù)也是分布式系統(tǒng)中比較常見的一類場(chǎng)景。在分布式系統(tǒng)中,通過使用命名服務(wù),客戶端應(yīng)用能夠根據(jù)指定名字來獲取資源或服務(wù)的地址,提供者等信息。被命名的實(shí)體通常可以是集群中的機(jī)器,提供的服務(wù)地址,遠(yuǎn)程對(duì)象等等——這些我們都可以統(tǒng)稱他們?yōu)槊郑∟ame)。其中較為常見的就是一些分布式服務(wù)框架中的服務(wù)地址列表。通過調(diào)用ZK提供的創(chuàng)建節(jié)點(diǎn)的API,能夠很容易創(chuàng)建一個(gè)全局唯一的path,這個(gè)path就可以作為一個(gè)名稱。
阿里巴巴集團(tuán)開源的分布式服務(wù)框架Dubbo中使用ZooKeeper來作為其命名服務(wù),維護(hù)全局的服務(wù)地址列表,點(diǎn)擊這里查看Dubbo開源項(xiàng)目。在Dubbo實(shí)現(xiàn)中:
服務(wù)提供者在啟動(dòng)的時(shí)候,向ZK上的指定節(jié)點(diǎn)/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個(gè)操作就完成了服務(wù)的發(fā)布。
服務(wù)消費(fèi)者啟動(dòng)的時(shí)候,訂閱/dubbo/${serviceName}/providers目錄下的提供者URL地址, 并向/dubbo/${serviceName} /consumers目錄下寫入自己的URL地址。
注意,所有向ZK上注冊(cè)的地址都是臨時(shí)節(jié)點(diǎn),這樣就能夠保證服務(wù)提供者和消費(fèi)者能夠自動(dòng)感應(yīng)資源的變化。
另外,Dubbo還有針對(duì)服務(wù)粒度的監(jiān)控,方法是訂閱/dubbo/${serviceName}目錄下所有提供者和消費(fèi)者的信息。
分布式通知/協(xié)調(diào)
ZooKeeper中特有watcher注冊(cè)與異步通知機(jī)制,能夠很好的實(shí)現(xiàn)分布式環(huán)境下不同系統(tǒng)之間的通知與協(xié)調(diào),實(shí)現(xiàn)對(duì)數(shù)據(jù)變更的實(shí)時(shí)處理。使用方法通常是不同系統(tǒng)都對(duì)ZK上同一個(gè)znode進(jìn)行注冊(cè),監(jiān)聽znode的變化(包括znode本身內(nèi)容及子節(jié)點(diǎn)的),其中一個(gè)系統(tǒng)update了znode,那么另一個(gè)系統(tǒng)能夠收到通知,并作出相應(yīng)處理
另一種心跳檢測(cè)機(jī)制:檢測(cè)系統(tǒng)和被檢測(cè)系統(tǒng)之間并不直接關(guān)聯(lián)起來,而是通過zk上某個(gè)節(jié)點(diǎn)關(guān)聯(lián),大大減少系統(tǒng)耦合。
另一種系統(tǒng)調(diào)度模式:某系統(tǒng)有控制臺(tái)和推送系統(tǒng)兩部分組成,控制臺(tái)的職責(zé)是控制推送系統(tǒng)進(jìn)行相應(yīng)的推送工作。管理人員在控制臺(tái)作的一些操作,實(shí)際上是修改了ZK上某些節(jié)點(diǎn)的狀態(tài),而ZK就把這些變化通知給他們注冊(cè)Watcher的客戶端,即推送系統(tǒng),于是,作出相應(yīng)的推送任務(wù)。
另一種工作匯報(bào)模式:一些類似于任務(wù)分發(fā)系統(tǒng),子任務(wù)啟動(dòng)后,到zk來注冊(cè)一個(gè)臨時(shí)節(jié)點(diǎn),并且定時(shí)將自己的進(jìn)度進(jìn)行匯報(bào)(將進(jìn)度寫回這個(gè)臨時(shí)節(jié)點(diǎn)),這樣任務(wù)管理者就能夠?qū)崟r(shí)知道任務(wù)進(jìn)度。
總之,使用zookeeper來進(jìn)行分布式通知和協(xié)調(diào)能夠大大降低系統(tǒng)之間的耦合
集群管理與Master選舉
集群機(jī)器監(jiān)控:這通常用于那種對(duì)集群中機(jī)器狀態(tài),機(jī)器在線率有較高要求的場(chǎng)景,能夠快速對(duì)集群中機(jī)器變化作出響應(yīng)。這樣的場(chǎng)景中,往往有一個(gè)監(jiān)控系統(tǒng),實(shí)時(shí)檢測(cè)集群機(jī)器是否存活。過去的做法通常是:監(jiān)控系統(tǒng)通過某種手段(比如ping)定時(shí)檢測(cè)每個(gè)機(jī)器,或者每個(gè)機(jī)器自己定時(shí)向監(jiān)控系統(tǒng)匯報(bào)“我還活著”。 這種做法可行,但是存在兩個(gè)比較明顯的問題:
集群中機(jī)器有變動(dòng)的時(shí)候,牽連修改的東西比較多。
有一定的延時(shí)。
利用ZooKeeper有兩個(gè)特性,就可以實(shí)時(shí)另一種集群機(jī)器存活性監(jiān)控系統(tǒng):
客戶端在節(jié)點(diǎn) x 上注冊(cè)一個(gè)Watcher,那么如果 x?的子節(jié)點(diǎn)變化了,會(huì)通知該客戶端。
創(chuàng)建EPHEMERAL類型的節(jié)點(diǎn),一旦客戶端和服務(wù)器的會(huì)話結(jié)束或過期,那么該節(jié)點(diǎn)就會(huì)消失。
例如,監(jiān)控系統(tǒng)在 /clusterServers 節(jié)點(diǎn)上注冊(cè)一個(gè)Watcher,以后每動(dòng)態(tài)加機(jī)器,那么就往 /clusterServers 下創(chuàng)建一個(gè) EPHEMERAL類型的節(jié)點(diǎn):/clusterServers/{hostname}. 這樣,監(jiān)控系統(tǒng)就能夠?qū)崟r(shí)知道機(jī)器的增減情況,至于后續(xù)處理就是監(jiān)控系統(tǒng)的業(yè)務(wù)了。
Master選舉則是zookeeper中最為經(jīng)典的應(yīng)用場(chǎng)景了。
在分布式環(huán)境中,相同的業(yè)務(wù)應(yīng)用分布在不同的機(jī)器上,有些業(yè)務(wù)邏輯(例如一些耗時(shí)的計(jì)算,網(wǎng)絡(luò)I/O處理),往往只需要讓整個(gè)集群中的某一臺(tái)機(jī)器進(jìn)行執(zhí)行,其余機(jī)器可以共享這個(gè)結(jié)果,這樣可以大大減少重復(fù)勞動(dòng),提高性能,于是這個(gè)master選舉便是這種場(chǎng)景下的碰到的主要問題。
利用ZooKeeper的強(qiáng)一致性,能夠保證在分布式高并發(fā)情況下節(jié)點(diǎn)創(chuàng)建的全局唯一性,即:同時(shí)有多個(gè)客戶端請(qǐng)求創(chuàng)建 /currentMaster 節(jié)點(diǎn),最終一定只有一個(gè)客戶端請(qǐng)求能夠創(chuàng)建成功。利用這個(gè)特性,就能很輕易的在分布式環(huán)境中進(jìn)行集群選取了。
另外,這種場(chǎng)景演化一下,就是動(dòng)態(tài)Master選舉。這就要用到?EPHEMERAL_SEQUENTIAL類型節(jié)點(diǎn)的特性了。
上文中提到,所有客戶端創(chuàng)建請(qǐng)求,最終只有一個(gè)能夠創(chuàng)建成功。在這里稍微變化下,就是允許所有請(qǐng)求都能夠創(chuàng)建成功,但是得有個(gè)創(chuàng)建順序,于是所有的請(qǐng)求最終在ZK上創(chuàng)建結(jié)果的一種可能情況是這樣: /currentMaster/{sessionId}-1 ,?/currentMaster/{sessionId}-2 ,?/currentMaster/{sessionId}-3 ….. 每次選取序列號(hào)最小的那個(gè)機(jī)器作為Master,如果這個(gè)機(jī)器掛了,由于他創(chuàng)建的節(jié)點(diǎn)會(huì)馬上小時(shí),那么之后最小的那個(gè)機(jī)器就是Master了。
在搜索系統(tǒng)中,如果集群中每個(gè)機(jī)器都生成一份全量索引,不僅耗時(shí),而且不能保證彼此之間索引數(shù)據(jù)一致。因此讓集群中的Master來進(jìn)行全量索引的生成,然后同步到集群中其它機(jī)器。另外,Master選舉的容災(zāi)措施是,可以隨時(shí)進(jìn)行手動(dòng)指定master,就是說應(yīng)用在zk在無法獲取master信息時(shí),可以通過比如http方式,向一個(gè)地方獲取master。
在Hbase中,也是使用ZooKeeper來實(shí)現(xiàn)動(dòng)態(tài)HMaster的選舉。在Hbase實(shí)現(xiàn)中,會(huì)在ZK上存儲(chǔ)一些ROOT表的地址和HMaster的地址,HRegionServer也會(huì)把自己以臨時(shí)節(jié)點(diǎn)(Ephemeral)的方式注冊(cè)到Zookeeper中,使得HMaster可以隨時(shí)感知到各個(gè)HRegionServer的存活狀態(tài),同時(shí),一旦HMaster出現(xiàn)問題,會(huì)重新選舉出一個(gè)HMaster來運(yùn)行,從而避免了HMaster的單點(diǎn)問題
分布式鎖
分布式鎖,這個(gè)主要得益于ZooKeeper為我們保證了數(shù)據(jù)的強(qiáng)一致性。鎖服務(wù)可以分為兩類,一個(gè)是保持獨(dú)占,另一個(gè)是控制時(shí)序。
所謂保持獨(dú)占,就是所有試圖來獲取這個(gè)鎖的客戶端,最終只有一個(gè)可以成功獲得這把鎖。通常的做法是把zk上的一個(gè)znode看作是一把鎖,通過create znode的方式來實(shí)現(xiàn)。所有客戶端都去創(chuàng)建 /distribute_lock 節(jié)點(diǎn),最終成功創(chuàng)建的那個(gè)客戶端也即擁有了這把鎖。
控制時(shí)序,就是所有視圖來獲取這個(gè)鎖的客戶端,最終都是會(huì)被安排執(zhí)行,只是有個(gè)全局時(shí)序了。做法和上面基本類似,只是這里 /distribute_lock 已經(jīng)預(yù)先存在,客戶端在它下面創(chuàng)建臨時(shí)有序節(jié)點(diǎn)(這個(gè)可以通過節(jié)點(diǎn)的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL來指定)。Zk的父節(jié)點(diǎn)(/distribute_lock)維持一份sequence,保證子節(jié)點(diǎn)創(chuàng)建的時(shí)序性,從而也形成了每個(gè)客戶端的全局時(shí)序。
分布式隊(duì)列
隊(duì)列方面,簡(jiǎn)單地講有兩種,一種是常規(guī)的先進(jìn)先出隊(duì)列,另一種是要等到隊(duì)列成員聚齊之后的才統(tǒng)一按序執(zhí)行。對(duì)于第一種先進(jìn)先出隊(duì)列,和分布式鎖服務(wù)中的控制時(shí)序場(chǎng)景基本原理一致,這里不再贅述。
第二種隊(duì)列其實(shí)是在FIFO隊(duì)列的基礎(chǔ)上作了一個(gè)增強(qiáng)。通常可以在 /queue 這個(gè)znode下預(yù)先建立一個(gè)/queue/num 節(jié)點(diǎn),并且賦值為n(或者直接給/queue賦值n),表示隊(duì)列大小,之后每次有隊(duì)列成員加入后,就判斷下是否已經(jīng)到達(dá)隊(duì)列大小,決定是否可以開始執(zhí)行了。這種用法的典型場(chǎng)景是,分布式環(huán)境中,一個(gè)大任務(wù)Task A,需要在很多子任務(wù)完成(或條件就緒)情況下才能進(jìn)行。這個(gè)時(shí)候,凡是其中一個(gè)子任務(wù)完成(就緒),那么就去 /taskList 下建立自己的臨時(shí)時(shí)序節(jié)點(diǎn)(CreateMode.EPHEMERAL_SEQUENTIAL),當(dāng) /taskList 發(fā)現(xiàn)自己下面的子節(jié)點(diǎn)滿足指定個(gè)數(shù),就可以進(jìn)行下一步按序進(jìn)行處理了。
原文地址:http://blog.csdn.net/xinguan1267/article/details/38422149