ZooKeeper食譜(六)

使用ZooKeeper構造高級別應用的指南

在這個文章中,你將會發現使用ZooKeeper來實現高級別功能的指南。全部的它們在客戶端上被實現而不須要ZooKeeper特別的支持.但願社區將注意到這些約定在客戶端庫裏來方便他們的使用而且促進標準化。html

 

其中一個關於ZooKeeper最有趣的事是儘管ZooKeeper使用異步的通知,你可使用它構造同步的一致性原語,例如隊列和鎖。正如你將要看到的同樣,這是可能的由於ZooKeeper對更新強加了一個總體的順序,和暴躁這個順序的機制。前端

注意下面的食譜試圖實用最掛實踐。特別地,他們避免投票,定時器或者任何其它致使產生「羊羣效應」的形成突發的事故和限制擴展性。node

有不少能夠被想到的有用的功能沒有包含在這 - 可撤銷的讀-寫優先鎖,只有一個例子。而且這裏提到的一些構件 - 特別是鎖 - 闡明瞭特定的觀點,儘管你可能找到其它構件,例如事件處理或隊列,執行相同功能的一個更實用的方法。一般,這部分的例子被設計用來刺激思想。apache

關於錯誤處理的重要注意事項

當實現這些食譜你必須處理可恢復的異常(參考 FAQ)。特別地,一些食譜利用了順序的短暫節點。當建立一個順序的短暫節點時,有一個錯誤的案例,當create()在服務端成功了可是在返回給客戶端這個節點的名字以前服務端掛掉了。當客戶端從新鏈接它的會話仍然是有效的而且,所以,這個節點沒有被刪除。這個意思是對客戶端來講它很難知道這個節點有沒有被建立。下面的食譜包含了處理這種狀況的方法。數據結構

取出便可用的應用:命名服務,配置,集羣管理

命名服務和配置管理是ZooKeeper的兩個主要應用。這兩上功能被ZooKeeper API直接提供。app

另外一個被ZooKeeper直接提供的是集羣管理。組被表示爲了一個節點。組中的成員在組節點下建立短暫的節點。不正常地失敗的組中的節點將被自動地刪除當ZooKeeper檢測到失敗。異步

屏障

分佈式的系統使用屏障來一組節點的進程,直到遇到知足的條件的時候全部節點才被容許繼續執行。在ZooKeeper中屏障經過指定一個屏障節點來實現。障礙在路上若是屏障節點存在。下面的它的僞代碼:分佈式

  1. 客戶端在屏障節點上調用ZooKeeper API的exists()方法,而且設置 watch 爲true.
  2. 若是exists()返回false,屏障消失而且客戶端繼續。
  3. 不然,若是exists()返回true,客戶端等待屏障節點的事件。
  4. 當監聽事件被觸發,客戶端再次調用exists()方法,再次等待直到屏障節點被刪除。

兩重屏障函數

兩重屏障使客戶端同步一個計算的開始和結束。當足夠的進程進入到屏障,進程開始他們的計算,而且一旦完成就離開屏障。這個食譜展現了怎麼做爲屏障使用ZooKeeper節點。ui

這個食譜中的僞代碼用b來表示一個屏障節點。每個客戶端進程 p 在進入的時候註冊到屏障節點上而且在離開的時候取消註冊。一個節點經過下面的Enter過程來註冊到屏障節點上,在繼續計算以前它等待直到 x 客戶端進程完成註冊(這裏的x取決於你的系統)。

Enter Leave

1. Create a name n = b+「/」+p

2. Set watch: exists(b + ‘‘/ready’’, true)

3. Create child: create( n, EPHEMERAL)

4. L = getChildren(b, false)

5. if fewer children in L than x, wait for watch event

6. else create(b + ‘‘/ready’’, REGULAR)

1. L = getChildren(b, false)

2. if no children, exit

3. if p is only process node in L, delete(n) and exit

4. if p is the lowest process node in L, wait on highest process node in L

5. else delete(n) if still exists and wait on lowest process node in L

6. goto 1

當進入的時候,全部進程監視一個準備好的節點,而且建立一個短暫節點做爲屏障節點的子節點,每個進程可是最後進入的屏障,而且等待準備的節點出如今第5行。建立第x個節點的進程,也就是最後一個節點。將看到x個節點在孩子列表中。而且建立準備結點。而後喚醒其它進程。注意等待進程只有在退出的時候才喚醒。因此等待是高效的。

在退出的時候。你不能使用一個標示,例如ready,由於你正在等待處理的節點離開。經過使用短暫節點。在進入屏障以後失敗的進程不能阻止正確的進程結束。當進程準備離開的時候。它們必須刪除進程節點而且等待其它進程作一樣的事情。

進程退出當沒有進程節點做爲b的孩子節點的時候。然而,爲了效率,你可使用最底的進程節點做爲準備標示。準備退出的全部其它進程監視最底的退出節點離開。而且最底節點的擁有者監視任一其它節點(爲了簡單選擇最高的)的離開。這意爲只一個節點喚醒當一個節點刪除的時候除了最後一個節點刪除。最後節點刪除的時候全部節點被喚醒。

隊列

分佈式隊列是一個廣泛的數據結構。爲了在ZooKeeper中實現一個分佈式隊列。首先指定一個znode持有隊列。隊列節點。分佈式的客戶端放一些東西進入隊列經過用以queue-結尾的路徑,序號和爲短暫的標示true調用create()。由於序號的標示被設置。新的路徑名字的形式爲_path-to-queue-node_/queue-X,這裏的X是自動增長的數字。一個想要從隊列中被刪除的節點調用ZooKeeper 的getChildren( ) 函數。在隊列中的節點都設置了監視器。而且開始處理最小數值的節點。客戶端不須要發起另外一個getChildren( )直到消耗到第一次調用getChildren( )返回的列表。若是沒有孩子在隊列節點中。讀進程等待一個監視通知來再次檢查隊列。

注意

如今有一個隊列實如今ZooKeeper食譜目錄下。它在發行版本的src/recipes/queue 目錄下。

優先隊列

爲了實現優先隊列,你只須要在通用的隊列上作兩個小的修改。首先,爲了增長一個隊列,路徑名字以"queue-YY"結尾,這裏的YY是元素的優先級,並且最小的數有最高的優先級(就像UNIX)。第二,當從隊列中刪除的時候。客戶端使用最新的孩子列表意爲着客戶端將使先前獲取的孩子列表無效若是觸發了這個隊列節點的監聽通知。

徹底分佈式鎖是全局同步的。意爲着在任什麼時候間的快照中沒有兩個客戶認爲它們持有相同的鎖。這些能夠實現使用ZooKeeper。就像優先隊列,先定義一個鎖節點。

注意如今有一個鎖實如今ZooKeeper食譜目錄下,它在發行版本的src/recipes/queue 目錄下。

客戶端想獲取鎖須要作下面的事情:

  1. 以參數"_locknode_/guid-lock-"同時設置sequence和ephemeral標識來調用create()。須要一個guid以防create()丟失。參考下面的注意事項。
  2. 在鎖節點上調用getChildren()而不設置監視標識(這對避免羊羣效應很重要)
  3. 若是在步驟1中建立的路徑名有最小的順序數字後綴,客戶端擁有這個鎖而且客戶端退出協議。
  4. 客戶端調用exists()並在鎖目錄中的下一個最小序列數字的路徑設置監視標識。
  5. 若是exists()返回false,跳到第2步。不然等待從前一個步驟中獲得路徑名的通知在進入步驟2以前。

解鎖協議是很是簡單的:將要釋放鎖的客戶端簡單地刪除他們在步驟1中建立的節點。

這裏須要幾件事須要注意:

  • 一個節點的刪除將只引發一個客戶端喚醒由於每個節點偏偏被一個客戶端監視着。用這種方式你避免了羊羣效應。
  • 這裏沒有投票或超時。
  • 由於你實現鎖的方式,很容易看到鎖競爭的數量,打斷鎖,調試鎖問題,等等。

可從新覆蓋的錯誤和GUID

  • 若是在調用create()的時候出現可從新覆蓋的錯誤,客戶端應該調用getChildren()而且檢查包含用來在路徑名的guid的節點。這處理在服務端create()成功可是在返回新節點的名字以前服務端崩潰的狀況。

共享鎖

你能夠實現共享鎖經過對鎖協議作一些小的改變:

獲取讀鎖 獲取寫鎖

1. 調用create()來建立一個路徑名 "guid-/read-"的爲節點。這個在後來的協議中使用鎖節點。確保設置了sequence 和 ephemeral標記。

2. 在節鎖節點上調用getChildren(),而不設置監視標識 - 它很重要,它避免羊羣效應。

3. 若是沒有一個路徑名以"write-"開頭的孩子而且有比步驟1建立的節點小的序列號,那麼客戶端就擁有了鎖而且可能退出協議。

4. 不然,調用監視標識的exists(),在有最小的序列號而且路徑名是以"write-"開頭的節點上設置。

5. 若是exists()返回 false,跳到步驟2。

6. 不然,在跳到步驟2以前等待來自上一個步驟中的路徑名的通知。

1. 調用create()來建立一個路徑名爲"guid-/write-"的節點。這是鎖節點。確保設置了sequence 和 ephemeral標記。

2. 在鎖節點上調用getChildren( )方法而不設置監視標記 - 它很重要,它避免羊羣效應。

3. 若是沒有孩子節點的序列號比步驟1中建立的節點低,那麼客戶端就獲取鎖而且客戶端退出這個協議。

4. 調用exists(),在下一個最小的序列號的節點上設置監視標識。

5. 若是exists() 返回false,跳到步驟2。不然,在跳到步驟2以前等待來自上一個步驟中的路徑名的通知。

注意:

  • 這個食譜它可能出現羊羣效應:當不少組的客戶端正在等待讀鎖,在最小序列號的"write-"節點被刪除的時候,這些客戶端幾乎同時獲取到了通知。實際上,這是合法的行爲:由於全部這些等待讀的客戶端應該被釋放由於它們擁有鎖。羊羣效應是指釋放一個"羊羣"當實際上只有一個或一小部分機器可能處理的時候。
  • 參考note for Locks關於怎麼使用guid

可撤消的共享鎖

對共享鎖作一些小的修改,經過修改共享鎖你可使用鎖可撤銷。

在步驟1中,同時獲取了讀和寫鎖,在調用create()後馬上調用getData()並設置監視器,若是客戶端隨後收到在步驟1中建立的節點的通知,它作另外一個getData()在這個節點上,同時設置監視器而且搜尋字符串「unlock」,這表示客戶端必須釋放鎖。這是由於根據共享鎖的協議,你能夠要求擁有鎖的客戶端放棄鎖經過在被鎖的節點上調用setData(),並寫「unlock」到這個節點上。

注意這個協議要求鎖持有者贊成釋放鎖。這樣的贊成是很重要的,特別是若是鎖持有者須要在釋放鎖以前作一些處理。固然你能夠一直實現帶着該死的激光束可撤消的鎖經過在你的協議中規定撤消者被容許刪除鎖節點若是在一段時間事後鎖沒有被鎖持有者刪除。

兩段式提交

一個兩段式提交協議是一種邏輯,它讓全部在分佈式系統中的的客戶端贊成要麼提交事務要麼取消事務。

在ZooKeeper中,你能夠實現一個兩段式提交經過用一個協調者建立一個事務節點,好比"/app/Tx",每個參與的站點的孩子節點,好比,"/app/Tx/s_i"。當協調者建立孩子節點,它沒有設置內容。一旦每個事務中的一方從協調者收到事務,站點讀每個孩子節點,而且設置一個監視器。每個站點而後處理查詢 和投票「提交」和「取消」經過寫它各自的節點。一旦寫完成,其它站點被通知,而且一旦全部的站點者投票,他們能夠決定是提交 仍是取消。注意一個節點可早點決定取消若是一些站點投票取消。

這個實現的一個有趣的地點是隻有協調的角色來根據站點組來決作決定,爲了建立ZooKeeper節點,爲了傳播事務到相應的站點。實際上,儘管傳播事務事務能夠被完成融通ZooKeeper經過寫它在事務節點上。

上面論討的方法有兩個缺點。一個是消息的複雜度,它是O(n²)。第二個是經過臨時節點檢測站點失效的不可能性。爲了使用臨時節點來檢測站點失效,它必須站點建立這個節點。

爲了解決第一個問題,只能夠只能讓協調者被通知事務節點的改變,而且通知節點一旦協調者收到一個決定。注意這個方法是可擴展的,可是它也更慢,由於它要求全部的通訊經過協調者。

爲了解決第二個問題,你可使協調者傳播事務到站點,而且使每個站點建立它的臨時節點。

領導者選舉

ZooKeeper作領導者選擇的一個簡單的方法是當建立表明揭底客戶端的節點時使用SEQUENCE|EPHEMERAL標記。這個思路是有一個znode,好比 「/election",每個znode建立一個帶着SEQUENCE|EPHEMERAL孩子節點"/election/guid-n_",有了順序標記,ZooKeeper自動地附加一個比選擇增長的都大的數字到一個"/election"的孩子上面。建立的最小順序號的節點就是領導者。

這仍是所有,監聽領導者的失效是很重要的,因此在當前領導者失效的狀況下一個新的客戶端被選舉出來。一個複雜的解決辦法是使全部的應用進程監聽當前最小的節點,而且檢查他們是否是新的領導者當最小的znode消失的時候(注意最小的znode將消失,若是領導者失敗,由於這個節點是臨時節點)。可是這致使一個羊羣效應:一旦當前領導者失敗,全部其它里程收到 一個通知,而後執行"/election" 的getChildren的方法 來獲取孩子列表。若是客戶端的數字是大的,它致使ZooKeeper服務端不得不處理不少次數字操做。爲了不羊羣效應,只監聽在znodes序列的下一個節點就足夠了。若是一個客戶端收到它監聽的節點消失了,那麼它變爲新的領導,一旦沒有更小的節點的狀況下。注意到這避免了羊羣效應經過不是全部的客戶端監聽同一個節點。

這裏是它的僞代碼:

假設ELECTION是應用的選擇的路徑。志願成爲一個領導者:

  1. 建立znode z,它的路徑是"ELECTION/guid-n_",而且設置SEQUENCE 和 EPHEMERAL 標識;
  2. 讓C成爲"ELECTION"的孩子,而且i是z的序號;
  3. 監聽"ELECTION/guid-n_j"的改變,這裏的j是最小(官網上說是最大(largest)的應該是錯的)的序序號,這樣j<i而且 n_j是C中的節點;

一旦收到 節點刪除的通知:

  1. 假如C是ELECTION的孩子節點
  2. 若是z是C中最小的節點,那麼執行選舉過程;
  3. 不然,監聽"ELECTION/guid-n_j"的改變,這裏的j是最小(官網上說是最大(largest)的應該是錯的)的序序號,這樣j<i而且 n_j是C中的節點;

注意:

  • 注意一個節點前端沒有節點不意爲着這個節點的建立者意識到它是當前的領導者。應用能夠考慮建立一個單獨的znode來通知領導者已經執行了領導選舉。
  • 參考 note for Locks關於怎麼使用節點中的guid
相關文章
相關標籤/搜索