?
閱讀指南——如何利用 Zookeeper 構(gòu)建上層應(yīng)用?
本文將帶你如何利用 Zookeeper 實(shí)現(xiàn)某些分布式應(yīng)用所必需的高級(jí)功能。所有功能均可以在客戶端按固定的模式實(shí)現(xiàn),不需要 Zookeeper 的特殊支持,也希望 Zookeeper 社區(qū)能將這些具有固定實(shí)現(xiàn)模式的功能集成到 Zookeeper 客戶端的程序庫中,可以簡(jiǎn)化 Zookeeper 的使用并且還能使某些功能的實(shí)現(xiàn)標(biāo)準(zhǔn)化。
即便 Zookeeper 本身使用異步通知( asynchronous ?notifications),但卻可以基于此構(gòu)建同步的( synchronous )一致性原語,如隊(duì)列和鎖。你將看到 Zookeeper 實(shí)現(xiàn)這些功能是完全可能的,因?yàn)?Zookeeper 提供了強(qiáng)制的全序更新,并對(duì)外提供了保序接口。?
注意下面的程序段試圖采取最佳的編程實(shí)踐,尤其是避免使用輪詢(polling),定時(shí)器(timers)和其他任何可能造成“羊群效應(yīng)(herd effect)”機(jī)制(“羊群效應(yīng)”一般會(huì)帶來網(wǎng)絡(luò)流量的突增,限制系統(tǒng)的可擴(kuò)展性)。
除了本文所列舉的功能,我們還可以想象出其他很多實(shí)用的功能,比如可撤銷的讀寫優(yōu)先鎖。本文提到的某些構(gòu)建方式——比如鎖,比較詳細(xì)的闡述了使用 Zookepper 的關(guān)鍵點(diǎn),其實(shí)你可以找到其他的例子,如事件處理和隊(duì)列,但是,本節(jié)中的例子只是模擬相關(guān)的功能,在具體實(shí)踐中需要考慮其他方面的因素。
開箱即用的應(yīng)用示例:命名服務(wù),配置管理,組關(guān)系管理
命名服務(wù)和配置管理是 Zookeeper 提供的最基本的應(yīng)用,這兩個(gè)功能可以直接用 Zookeeper 提供的 API 實(shí)現(xiàn)。
另外一個(gè)可以直接使用的功能是組關(guān)系管理,組在 Zookeeper 由一個(gè) Znode 表示,組中的某個(gè)成員可以用組節(jié)點(diǎn)下的臨時(shí)節(jié)點(diǎn)(Ephemeral Nodes)表示,當(dāng) Zookeeper 檢測(cè)到節(jié)點(diǎn)故障時(shí),節(jié)點(diǎn)成員中不正常的節(jié)點(diǎn)將會(huì)被自動(dòng)地移除。
屏障(Barriers)
分布式系統(tǒng)使用屏障( barriers )來阻塞某一節(jié)點(diǎn)集的任務(wù),直到滿足特定的條件該節(jié)點(diǎn)集的所有節(jié)點(diǎn)才能正常向前推進(jìn),就像屏障一樣,在當(dāng)條件不滿足時(shí),屏障阻塞了任務(wù)的執(zhí)行,只有當(dāng)條件滿足后屏障才會(huì)被拆除,各節(jié)點(diǎn)才能推進(jìn)自己正在執(zhí)行的任務(wù)。Zookeeper 中實(shí)現(xiàn)屏障時(shí)指定一個(gè)屏障節(jié)點(diǎn)(barrier node),如果屏障節(jié)點(diǎn)存在,屏障就會(huì)生效,下面是偽代碼:
-
客戶端在屏障節(jié)點(diǎn)上調(diào)用 ZooKeeper API?? exists(), watch 設(shè)置為 true .
-
如果? exists() ?返回 false,屏障消失,客戶端可以推進(jìn)的自己的工作。
-
否則,? exists() ?返回 true,客戶端等待屏障節(jié)點(diǎn)上監(jiān)聽事件的到來。
-
如果監(jiān)聽事件被觸發(fā),客戶端重新執(zhí)行? exists( ), ?再一次重復(fù)上述 1-3 步,直到屏障節(jié)點(diǎn)被移除。
?
(雙屏障)Double Barriers
雙屏障(Double barriers)使得所有客戶端在進(jìn)入和結(jié)束某一計(jì)算任務(wù)時(shí)都會(huì)得到同步。當(dāng)足夠的進(jìn)程processes(注:此處指節(jié)點(diǎn))加入到屏障時(shí),才啟動(dòng)任務(wù),然后當(dāng)任務(wù)完成時(shí),離開屏障區(qū),下面的代碼段示意如何使用 Zookeeper 創(chuàng)建屏障節(jié)點(diǎn)。
偽代碼中屏障節(jié)點(diǎn)用? b ?表示,每個(gè)客戶端進(jìn)程(節(jié)點(diǎn))?? p ?在進(jìn)入屏障節(jié)點(diǎn)時(shí)注冊(cè)事件,然后在離開時(shí)取消注冊(cè)事件。進(jìn)入屏障節(jié)點(diǎn)注冊(cè)事件的代碼如下表的? Enter 程序段所示, ?在繼續(xù)處理任務(wù)之前,它將等待客戶端? x ?進(jìn)程的注冊(cè)。(此處的? x ?由你針對(duì)自己的系統(tǒng)決定)
Enter | Leave |
|
|
在進(jìn)入屏障時(shí),所有的進(jìn)程(節(jié)點(diǎn))監(jiān)視一個(gè)準(zhǔn)備好的節(jié)點(diǎn)(屏障節(jié)點(diǎn)),并創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn)作為屏障節(jié)點(diǎn)的孩子。除了最后進(jìn)入屏障的節(jié)點(diǎn)外,每個(gè)進(jìn)程(節(jié)點(diǎn))都等待屏障節(jié)點(diǎn),直到第 5 行的條件出現(xiàn)。該進(jìn)程(節(jié)點(diǎn))創(chuàng)建第 x 個(gè)節(jié)點(diǎn)——即最后的進(jìn)程(節(jié)點(diǎn)),它將會(huì)看到 x 個(gè)節(jié)點(diǎn),并喚醒其他進(jìn)程(節(jié)點(diǎn)),注意,所有的等待進(jìn)程(節(jié)點(diǎn))只是在退出的時(shí)候被喚醒,所以等待還是很高效的。
在退出屏障時(shí),你不能設(shè)置 諸如? ready ?的標(biāo)志,因?yàn)槟阍诘却M(jìn)程節(jié)點(diǎn)退出,通過使用臨時(shí)節(jié)點(diǎn),進(jìn)入屏障后失效的進(jìn)程節(jié)點(diǎn)并不會(huì)阻止其他運(yùn)行正確的節(jié)點(diǎn)完成任務(wù)。當(dāng)進(jìn)程節(jié)點(diǎn)準(zhǔn)備推出屏障區(qū)時(shí),它必須刪除它的進(jìn)程節(jié)點(diǎn),并等待其他進(jìn)程刪除各自的進(jìn)程節(jié)點(diǎn)。
當(dāng) b 沒有的進(jìn)程子節(jié)點(diǎn)時(shí),進(jìn)程(節(jié)點(diǎn))就會(huì)退出屏障區(qū)。然而,為了效率起見,你可以使用序號(hào)最低的進(jìn)程節(jié)點(diǎn)作為 ready 標(biāo)志。所有其他準(zhǔn)備退出屏障區(qū)的進(jìn)程(節(jié)點(diǎn))都監(jiān)視序號(hào)最低的將要退出進(jìn)程(節(jié)點(diǎn))消失,序號(hào)最低的進(jìn)程節(jié)點(diǎn)的擁有者則就等待其他任何一個(gè)節(jié)點(diǎn)的消失(選擇序號(hào)最高進(jìn)程節(jié)點(diǎn))。這意味著除了最后的一個(gè)進(jìn)程節(jié)點(diǎn)外,其他的每個(gè)進(jìn)程節(jié)點(diǎn)被刪除時(shí)只要喚醒一個(gè)進(jìn)程節(jié)點(diǎn)即可,當(dāng)它被刪除時(shí)就會(huì)喚醒其他的進(jìn)程節(jié)點(diǎn)。
隊(duì)列(Queues)
分布式隊(duì)列是通用的數(shù)據(jù)結(jié)構(gòu),為了在 Zookeeper 中實(shí)現(xiàn)分布式隊(duì)列,首先需要指定一個(gè) Znode 節(jié)點(diǎn)作為隊(duì)列節(jié)點(diǎn)(queue node), 各個(gè)分布式客戶端通過調(diào)用 create() 函數(shù)向隊(duì)列中放入數(shù)據(jù),調(diào)用create()時(shí)節(jié)點(diǎn)路徑名帶"queue-"結(jié)尾,并設(shè)置順序和臨時(shí)( sequence ?and ephemeral )節(jié)點(diǎn)標(biāo)志。 由于設(shè)置了節(jié)點(diǎn)的順序標(biāo)志,新的路徑名具有以下字符串模式:"_path-to-queue-node_/queue-X",X 是唯一自增號(hào)。需要從隊(duì)列中移除數(shù)據(jù)的客戶端首先調(diào)用? getChildren( ) ?函數(shù),同時(shí)在隊(duì)列節(jié)點(diǎn)(queue node)上將? watch ?設(shè)置為 true,并處理最小序號(hào)的節(jié)點(diǎn)(即從序號(hào)最小的節(jié)點(diǎn)中取數(shù)據(jù))。客戶端不需要再一次調(diào)用? getChildren( ), 隊(duì)列中的數(shù)據(jù)獲取完。如果隊(duì)列節(jié)點(diǎn)中沒有任何子節(jié)點(diǎn),讀取隊(duì)列的客戶端需要等待隊(duì)列的監(jiān)視事件通知。
Priority Queues
為了實(shí)現(xiàn)優(yōu)先隊(duì)列,你在普通隊(duì)列上只需要簡(jiǎn)單的改變兩處地方,首先,在某一元素被加入隊(duì)列時(shí),路徑名以 "queue-YY" 結(jié)尾,YY 表示優(yōu)先級(jí),YY越小優(yōu)先級(jí)越高,其次,從隊(duì)列中移除一個(gè)元素時(shí),客戶端需要使用最新的孩子節(jié)點(diǎn)列表,這意味著如果隊(duì)列節(jié)點(diǎn)上監(jiān)視通知被觸發(fā),客戶端需要讓先前獲取的孩子節(jié)點(diǎn)列表無效。
鎖(Locks)
完全分布式鎖是全局同步的,這意味著在任何時(shí)刻沒有兩個(gè)客戶端會(huì)同時(shí)認(rèn)為它們都擁有相同的鎖,使用 Zookeeper 可以實(shí)現(xiàn)分布式鎖,和優(yōu)先隊(duì)列一樣,我們需要首先定義一個(gè)鎖節(jié)點(diǎn)(lock node)。
需要獲得鎖的客戶端按照以下步驟來獲取鎖:
-
調(diào)用? create( ), 參數(shù) pathname 為 "_locknode_/lock-",并設(shè)置? sequence? 和? ephemeral ?標(biāo)志。
-
在所節(jié)點(diǎn)(lock node)上調(diào)用? getChildren( ) ?,不需要設(shè)置監(jiān)視標(biāo)志。 (為了避免“羊群效應(yīng)”).
-
如果在第 1 步中創(chuàng)建的節(jié)點(diǎn)的路徑具有最小的序號(hào)后綴,那么該客戶端就獲得了鎖。
-
客戶端調(diào)用? exists( ) ?,并在鎖目錄路徑中下一個(gè)最小序號(hào)的節(jié)點(diǎn)上設(shè)置監(jiān)視標(biāo)志。
-
如果? exists( ) ?返回 false,跳轉(zhuǎn)至第 2 步,否則,在跳轉(zhuǎn)至第 2 步之前等待前一部路徑上節(jié)點(diǎn)的通知消息。
解鎖協(xié)議非常簡(jiǎn)單:需要釋放鎖的客戶端只需要?jiǎng)h除在第 1 步中創(chuàng)建的節(jié)點(diǎn)即可。
注意事項(xiàng):
-
一個(gè)節(jié)點(diǎn)的刪除只會(huì)導(dǎo)致一個(gè)客戶端被喚醒,因?yàn)槊總€(gè)節(jié)點(diǎn)只被一個(gè)客戶端監(jiān)視,這避免了羊群效應(yīng)。
-
沒有輪詢和超時(shí)。
-
根據(jù)你實(shí)現(xiàn)鎖的方式不同,不同的實(shí)現(xiàn)可能會(huì)帶來大量的鎖競(jìng)爭(zhēng),鎖中斷,調(diào)試鎖等等。
Shared Locks
在基本的鎖協(xié)議之上,你只需要做一些小的改變就可以實(shí)現(xiàn)共享鎖(shared locks):
獲取讀鎖: | 獲取寫鎖: |
|
|
Recoverable Shared Locks
對(duì)共享鎖做一些細(xì)小的改變,我們就可以使共享鎖變成可撤銷的共享鎖:
在第 1 步,在獲取讀者和寫者的鎖協(xié)議中,在調(diào)用? create( ) 后, 立即調(diào)用 getData( ) ,并設(shè)置監(jiān)視。如果客戶端稍后收到了它在第一步創(chuàng)建節(jié)點(diǎn)的通知,它會(huì)再一次在該節(jié)點(diǎn)上調(diào)用? getData( ) ,并設(shè)置監(jiān)視,查找 “unlock” 串。該信號(hào)會(huì)通知客戶端必須釋放鎖。這是因?yàn)椋罁?jù)共享鎖協(xié)議,你可以通過在鎖節(jié)點(diǎn)(lock node)上調(diào)用 setData()(將“unlock”寫入該節(jié)點(diǎn)) 請(qǐng)求擁有該鎖的客戶端放棄該鎖 。
注意該協(xié)議要求鎖的擁有者也同意釋放該鎖,該協(xié)定非常重要,尤其是鎖的擁有者需要在釋放該鎖前做一些處理。 當(dāng)然,你也可以通過約定“撤銷者可以在鎖的擁有者一段時(shí)間沒有刪除該鎖的情況下刪除該鎖節(jié)點(diǎn)”來實(shí)現(xiàn)可撤銷的共享鎖。
兩階段提交(Two-phased Commit)
兩階段提交協(xié)議可以讓分布式系統(tǒng)的所有客戶端決定究竟提交某一事務(wù)或還是終止該事務(wù)。
在 Zookeeper 中,你可以讓協(xié)調(diào)者(coordinator)創(chuàng)建事務(wù)節(jié)點(diǎn),比如,"/app/Tx",從而實(shí)現(xiàn)一個(gè)兩階段提交協(xié)議。 當(dāng)協(xié)調(diào)者(coordinator)創(chuàng)建了子節(jié)點(diǎn)時(shí),子節(jié)點(diǎn)內(nèi)容是未定義的,由于每個(gè)事務(wù)參與方都會(huì)從協(xié)調(diào)者接收事務(wù),參與方讀取每個(gè)子節(jié)點(diǎn)并設(shè)置監(jiān)視。然后每個(gè)參與方通過向與自身相關(guān)的 Znode 節(jié)點(diǎn)寫入數(shù)據(jù)來投票“提交(commit)”或“中止(abort)”事務(wù)。一旦寫入完成,其他的參與方會(huì)被通知到,當(dāng)所有的參與方都投完票后,協(xié)調(diào)者就可以決定究竟是“提交(commit)”或“中止(abort)”事務(wù)。注意,如果某些參與方投票“中止”,節(jié)點(diǎn)是可以決定提前“中止”事務(wù)的。
該實(shí)現(xiàn)方法有趣的地方在于協(xié)調(diào)者的唯一作用是決定參與方的組(the group of sites),創(chuàng)建 Zookeeper 節(jié)點(diǎn), 將事務(wù)傳播到相應(yīng)的參與方,實(shí)際上,Zookeeper 可以通過將消息寫入事務(wù)節(jié)點(diǎn)來傳播事務(wù)。
上述討論的方法存在兩個(gè)明顯的缺點(diǎn),一是消息的復(fù)雜性,復(fù)雜度為 O(n2),另外一個(gè)是僅通過臨時(shí)節(jié)點(diǎn)不能判斷某些參與方是否失效,為了利用臨時(shí)節(jié)點(diǎn)檢測(cè)參與方是否失效,必須參與方創(chuàng)建該節(jié)點(diǎn)。
為了解決第一個(gè)問題,你可以將系統(tǒng)設(shè)置成只有一個(gè)協(xié)調(diào)者可以收到事務(wù)節(jié)點(diǎn)狀態(tài)的變化,一旦協(xié)調(diào)者達(dá)成意見后通知其他參與方, 該方法可擴(kuò)展性較強(qiáng),但是速度很慢,因?yàn)樗械耐ㄐ哦贾赶騾f(xié)調(diào)者。
為了解決第二個(gè)問題,你可以讓參與方把事務(wù)傳播到參與方,并讓每個(gè)參與方創(chuàng)建自己的臨時(shí)節(jié)點(diǎn)。
Leader 選舉(Leader Election)
Zookeeper 實(shí)現(xiàn) Leader 選舉簡(jiǎn)單做法是在創(chuàng)建代表 “proposals” 客戶端的 Znode 節(jié)點(diǎn)時(shí)設(shè)置? SEQUENCE|EPHEMERAL ?標(biāo)志。基本想法是創(chuàng)建一個(gè)節(jié)點(diǎn),比如 "/election",然后在創(chuàng)建子節(jié)點(diǎn)時(shí)"/election/n_"設(shè)置標(biāo)志 SEQUENCE|EPHEMERAL. 當(dāng)設(shè)置順序節(jié)點(diǎn) SEQUENCE 標(biāo)志時(shí),Zookeeper 會(huì)在 "/election" 子節(jié)點(diǎn)的創(chuàng)建過程中自增子節(jié)點(diǎn)名稱后綴的序號(hào),最小后綴序號(hào)的 Znode 節(jié)點(diǎn)表示Leader。
然而,還沒完,監(jiān)視 Leader 失效也是非常重要的,當(dāng)前的 Leader 失效后需要一個(gè)新的客戶端起來接替舊的 Leader 的位置。一個(gè)簡(jiǎn)單的方式是讓所有的應(yīng)用進(jìn)程監(jiān)視當(dāng)前序號(hào)最小的 Znode 節(jié)點(diǎn), 并在當(dāng)前 序號(hào)最小的 Znode 節(jié)點(diǎn)失效是檢查他們是否為新的 Leader(注意當(dāng)前序號(hào)最小的節(jié)點(diǎn)可能會(huì)隨著 Leader 的消失而消失,他們可能是該Leader 節(jié)點(diǎn)的臨時(shí)子節(jié)點(diǎn)). 但是這會(huì)導(dǎo)致'羊群效應(yīng)(herd effect)":在當(dāng)前 Leader 失效后,其他所有的進(jìn)程(節(jié)點(diǎn))將會(huì)收到通知,并在 "/election" 節(jié)點(diǎn)上執(zhí)行 getChildren()來獲取"/election"節(jié)點(diǎn)的子節(jié)點(diǎn)列表,如果客戶端數(shù)目很大,它會(huì)使得Zookeeper服務(wù)器處理的操作次數(shù)急劇上升。為了避免羊群效應(yīng),客戶端只需要監(jiān)視 Znode 節(jié)點(diǎn)中的下一個(gè)節(jié)點(diǎn)就足夠。如果某個(gè)客戶端收到了它正在監(jiān)視的節(jié)點(diǎn)消失的通知,它將成為新的 Leader,因?yàn)榇藭r(shí)沒有其它的 Znode 節(jié)點(diǎn)的序號(hào)比它小。所以這就避免了羊群效應(yīng),并且客戶端也沒有必要監(jiān)視同一個(gè)最小的 Znode 節(jié)點(diǎn)。
以下是偽代碼:
假設(shè) ELECTION 成為Leader 選舉應(yīng)用的路徑,對(duì)于想要成為 Leader 的 Volunteer而言:
-
創(chuàng)建 Znode 節(jié)點(diǎn) z,路徑名稱為"ELECTION/n_"并設(shè)置 SEQUENCE 和 EPHEMERAL 標(biāo)志。
-
假設(shè) C 是"ELECTION"的子節(jié)點(diǎn)集合,? i 是 z 節(jié)點(diǎn)的序號(hào)。
-
監(jiān)視節(jié)點(diǎn) "ELECTION/n_j" 的改變,j 是滿足 j < i 最小的序號(hào),n_j 是 C 節(jié)點(diǎn)集合中的某個(gè)節(jié)點(diǎn)。
當(dāng)收到 Znode 節(jié)點(diǎn)刪除的通知時(shí):
-
假設(shè) C 是 “ELECTION” 新的子節(jié)點(diǎn)集合。
-
如果 z 是 C 中的最小節(jié)點(diǎn),則執(zhí)行 Leader 選舉流程。
-
否則,監(jiān)視節(jié)點(diǎn) "ELECTION/n_j" 的改變,j 是滿足 j < i 最小的序號(hào),n_j 是 C 節(jié)點(diǎn)集合中的某個(gè)節(jié)點(diǎn)。
注意,在子節(jié)點(diǎn)列表中沒有先遣節(jié)點(diǎn)的 Znode 并不意味著該節(jié)點(diǎn)的創(chuàng)建者知道它就是當(dāng)前的Leader,應(yīng)用程序可能需要考慮創(chuàng)建一個(gè)單獨(dú)的 Znode 來確認(rèn)該 Leader 已經(jīng)執(zhí)行了選舉流程。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
