ZooKeeper開發者指南(三)

引言html

這個文檔是爲了想利用ZooKeeper的協調服務來建立分佈式應用的開發者提供的指南。它包括概念和實踐的信息。node

這個文檔的一開始的的四部分呈現了不一樣ZooKeeper高級概念的的討論。理解Zookeeper是怎麼工做的和如何使用它同等重要。它不包含源碼,可是它確實假設你熟悉分佈式計算的問題。在這第一組的部分是:算法

  • ZooKeeper數據模型
  • ZooKeeper會話
  • ZooKeeper監聽
  • 一致性保證

下面的四部分提供了實踐編程信息。他們是:數據庫

  • 構造阻塞:ZooKeeper操做的指南
  • 綁定
  • 程序結構,和簡單的例子
  • 陷阱:常見問題和故障排除

本書最後的附錄包含其它有用的ZooKeeper相關的信息express

本文檔的大部分信息能夠做爲獨立的參考材料。然而,在開始你第一個ZooKeeper應用以前,你應該至少閱讀ZooKeeper數據模型和ZooKeeper基本操做的章節。同時,簡單的編程例子對於理解ZooKeeper客戶端應用的基本結構是不幫助的。apache

ZooKeeper數據模型編程

ZooKeeper有一個層次結構的命名空間,就像是一個分佈的文件系統。惟一的不一樣是每個節點能夠有一個和它關聯的數據,孩子節點也是。它好像是有一個文件系統容許文件是一個目錄。節點的路徑老是表達爲一個典型的絕對的斜線分隔的路徑:沒有相對路徑。任何nuicode字符能夠被用在路徑中同時受如下的約束:緩存

  • null字符(\u0000)多是路徑名字的一部分。(在C綁定裏面有問題)
  • 下面的字符不能被使用由於它們不能被很好的展現,或渲染得很混亂: \u0001 - \u001F和\u007F - \u009F
  • 下面的字段不容許:\ud800 - uF8FF, \uFFF0 - uFFFF
  • "."字符能夠被用來做爲名字的一部分。可是"."和".."不能單獨被用來表示一個節點的路徑,由於ZooKeeper不能用相對路徑。下面的將會是不合法的:"/a/b/./'c"或者」/a/b/../c「.
  • "zookeeper"是保留字

ZNodes
每個ZooKeeper樹中的每個節點被稱爲znode。Znodes維護了一個包括數據改變,acl改變的版本號的數據結構。這個數據結構一樣也有時間戳。版本號和時間戳,容許ZooKeeper校驗緩存和協調更新。每次znode的數據改變,版本號相應地增長。例如,安全

每當客戶端檢索數據,它一樣也收到數據的版本號。當客戶端執行一個更新或刪除,它必須提供正在改變的znode的版本號。若是它提供了版本號和實際的版本號不匹配,更新將會失敗。(這個行爲能夠被覆蓋。更多信息請參考...)服務器

注意

在分佈式應用的工程中,單詞node被能夠稱爲一個真實的主機,一個服務器,一個集羣中的成員,一個客戶進程,等等。在ZooKeeper的文檔中,znodes指的是數據節點。Servers指的是組成ZooKeeper服務的機器;quorum peers指的是組成集羣的servers;

client指的是任何使用ZooKeeper服務的主機或進程。

Znodes是開發者主要訪問的實體。他們有一些在這裏值得提起的特性。

Watches

客戶端能夠在znodes上設置監視器(watches)。這個znode的改變將觸發這個監視器而後清除這個監視器。當一個監視器被觸發,ZooKeeper給客戶端發送一個通知。更多關於監視器的信息能夠在ZooKeeper Watches部分找到。

Data Access

存儲在命名空間中的每個znode的數據被原子性地讀和寫。讀獲取全部跟這個znode關聯的全部數據,寫替換全部的數據。每個節點有一個訪問控制列表(ACL)限制誰能夠作和能夠作什麼 。

ZooKeeper不是設計用來做爲一個數據庫或大對象的存儲。相反地,它管理協調數據。這些數據能夠是配置,狀態信息等等。不一樣形式的協調數據有一個共同的特性就是它們相對來講數據量小:以千字節爲單位。ZooKeeper客戶端和服務端實現必需檢查確保znode的數據小於1M,可是數據平均來講應該小於這個值。對相對大的數據的操做將引發一些操做耗費比其它更多的時間而且將影響一些操做的延遲,由於須要更多的時間在網絡上移動數據和移動到存儲媒介上。若是須要存儲大數據,一般處理這種數據的模式是把它們存儲在容量存儲系統上。例如NFS或HDFS,而且在ZooKeeper上存儲指針。

Ephemeral Nodes

 ZooKeeper也有短暫節點的概念。這些節點只要建立它的會話是活躍的就會一直存在。當會話結束的時候,這個節點就會被刪除。由於這樣的行爲特性,短暫的節點不容許有孩子節點。

Sequence Nodes - 惟一命名

當建立一個znode你也能夠要求ZooKeeper追加一個單調遞增的計數器在路徑的結尾。這個計數器對父節點來講是惟一的。這個計數器的格式是%010d -- 也就是說10位數和0(數字0)襯墊(不足10位補0)(計算器被格式化爲這種形式是爲了簡化存儲)。也就是"<path>0000000001"。參考Queue Recipe獲取這個特性的例子。注意:被用來存儲下一個序列數字的計算器是一個被父節點維護的有符號的(signed)int,計算器將會溢出當增長超過2147483647(產生一個名字」<path>-2147483647「)。

Time in ZooKeeper

ZooKeeper以多種方式記錄時間:

  • Zxid

每次改變ZooKeeper狀態將會收到一個zxid形式的標記(ZooKeeper的事務Id)。這暴露了ZooKeeper的全部改變的總序列。每個改變將有一個惟一和zxid而且若是zxid1比zxid2小那麼zxid1在zxid2以前發生(happend before zxid2).

  • 版本號

一個節點每改變一次將引發 這個節點的版本號增長一次。這三個版本號是version(znode的數據改變的次數),cversion(znode字節點的改變的次數),和aversion(znode節點的ACL改變的次數)。

  • Ticks

當使用多服務器的ZooKeeper,服務器之間使用ticks來定義好比狀態上傳,會話超時,節點以前是鏈接超時等等的時間。tick時間只經過最小的會話超時時間來暴露(2倍的tick時間);若是一個客戶端請求會話超時小於最小的會話超時時間,那麼服務端將會告訴客戶端真實的會話超時時間爲最小的會話超時時間。

  • 真實時間(Real time)

ZooKeeper不使用真實的時間或時鐘時間,除了把時間戳在znode建立和修改的時候加入到數據結構。

ZooKeeper數據結構

 

ZooKeeper中的每個znode的數據結構是由下面的字段組成的:

  • czxid

建立znode時的zxid

  • mzxid

最後修改znode的zxid

  • ctime

從znode建立以來的毫秒時間

  • mtime

從znode最後修改以來的毫秒時間

  • version

znode數據改變的次數

  • cversion

znode孩子節點改變的次數

  • aversion

znode的ACL改變的次數

  • ephemeralOwner

若是是一個短暫節點,這個值就是znode的擁有者的會話id。若是不是短暫節點,它的值爲0。

  • dataLength

znode的數據字段的長度

  • numChildren

znode孩子節點的數量

ZooKeeper Sessions

一個ZooKeeper客戶端經過使用一個語言綁定建立一個握手來創建和ZooKeeper服務的會話。一旦創建,處理器以CONNECTING狀態開始而且客戶端庫試圖鏈接組成ZooKeeper服務的其中一個服務端,這時它就變成CONNECTED狀態。在正常操做下將會處於這二者之中的狀態。若是發生不可恢復的錯誤,例如會話過時或受權失敗,若是若是應用顯式地關閉了此次握手。此次握手將會變成CLOSED狀態。下面的圖表展現了ZooKeeper客戶端可能出現的狀態轉換。

爲了建立客戶端會話應用代碼必需提供一個包括以逗號分割主機:端口(host:port)列表對組成的鏈接字符串,每個對應一個ZooKeeper服務端(例如:"127.0.0.1:4545"或者」127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002「)。ZooKeeper客戶端庫將挑選一個任一的服務端而且試圖進行鏈接。若是這個鏈接失敗,或者若是由於任何緣由客戶端斷開鏈接,客戶端就自動地嘗試鏈接列表中的下一個服務端,直到(從新)創建一個鏈接。

3.2.0新加 一個可選的"chroot"後綴能夠加入到鏈接字符串。這將會運行客戶端命令並相對於這個root解釋全部的路徑(和nuix的chroot命令類似)。若是使用例子看起來像這樣:"127.0.0.1:4545/app/a"或者"127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a"。這裏客戶端將會以"/app/a"爲根目錄而且全部的路徑將會是相對於這個根目錄 - 例如 getting/setting/等等"/foo/bar"結果將是在」/app/a/foo/bar"上操做(從服務端的角度來看)。這個特色在一個特定ZooKeeper服務的每個用戶是不一樣根目錄的多用戶環境是很是有用的。這使重用變得更簡單由於每個用戶能夠改變他的/她的應用使它好像在"/"的根目錄下,同時真實的路徑(也就是 /app/a)能夠被肯定在部署的時候。

當客戶端獲得一個到服務端句柄,ZooKeeper建立一個ZooKeeper會話,以64位的數字表示,分配給客戶端。若是客戶端鏈接到不一樣的ZooKeeper服務端,它將會發送會話id做爲鏈接握手中的一部分。做爲一個安全措施,服務端爲會話Id建立一個密碼任何ZooKeeper服務端能夠用來校驗。這個密碼和會話id被髮送到客戶端當鏈接創建的時候。當和新的服務端從新創建鏈接的時候客戶端發送這個密碼和會話id到服務端。

ZooKeeper客戶端庫建立ZooKeeper會話的參數中的一個是以毫秒錶示的會話超時時間。客戶端發送一個請求超時時間,服務端在超時時間內回覆客戶端。目前的實現要求這個超時時間最小是2位的tickTime(在服務端的配置文件裏設置),最多20倍的tickTime.ZooKeeper客戶端API容許訪問這個超時時間。當一個客戶端(會話)被ZK服務集羣隔離開來,它將開始搜索在會話創建裏的服務器列表。最終,當客戶端和任一服務端的鏈接從新創建,這個會話將要麼再次轉變爲「connected」狀態(若是在會話超時時間內創建鏈接)要麼轉變爲"過時"狀態(若是在會話超時後創建鏈接)。不建議爲斷開創建一個新的會話對象(一個新的ZooKeeper.class或zookeeper句柄)。ZK客戶端將爲你處理重連。特別地咱們把啓發式算法內建到客戶端庫來處理像"羊羣效應"(herd effect),等等,只有當你被通知了會話到期了才新建一個會話(強制性的)。

會話到期時間被ZooKeeper集羣自己管理,而不是客戶端。當客戶端跟ZK集羣創建一個會話,它提供一個"過時時間"值。這個值被集羣決定客戶端會話何時過時。過時發生當集羣不能聽到客戶端的消息時在指定的會話超時週期內(也就是沒有心跳)。當會話過時集羣將會刪除全部的屬於這個會話的短暫節點並通知全部的連接的客戶端這一改變(任何監視這些節點的客戶端)。這時會話過時的會話的客戶端仍然和集羣處於斷開狀況。它將不會被通知會話過時只到/除非它和集羣從新創建連接。客戶端一直處於斷開狀態只到它和集羣從新創建連接。這時過時會話的監視器將會收到"會話過時"通知。

對於一個會話過時的狀態轉換能夠被過時會話的監視器看到的例子:

  1. 'connected':會話被創建而且客戶端正在和集羣通訊(客戶端/服務端的通訊正在正常地操做)
  2. .... 客戶端被集羣隔離
  3. 'disconnected':客戶端已經和集羣失去連接
  4. .... 時間流逝,在'超時時間'週期事後集羣使會話過時,客戶端不會看到任何東西由於它已經和集羣失去連接了
  5. .... 時間流逝,客戶端恢復了和集羣的網絡層的鏈接
  6. 'expired':最終客戶端恢復了和集羣的連接,而後它被通知到已通過期。

另外一個ZooKeeper的會話創建和參數是默認的監視器。監視器被通知當在客戶端發生任何改變。例如若是客戶端失去了服務端的鏈接它將會被通知,或者客戶端的會話過時,等等。這個監視器應該考慮初始狀態到失去鏈接的狀態(也就是說在任何狀態改變前事務被客戶端庫送到觀察者)。在一個新鏈接的狀況下,第一個送給觀察者的事件一般是會話創建事件。

會話經過客戶端發送請求保持存活。若是會話空閒一段超時會話的時間,客戶端將發送一個PING請求來保持會話是活着的。這個PING請求不只使ZooKeeper服務端知道客戶端是仍然存活着,它也使客戶端檢驗到ZooKeeper服務端的鏈接仍然活躍。PING的時機是至關保守以使確保合理的時間來檢測一個死去的鏈接和從新鏈接一個新的服務端。

一旦到服務端的鏈接成功地創建(connected)這裏有最基本的客戶端庫產生鏈接的兩個例子,當或者同步或異步的操做被執行而且下面的其中一個持有:

  1. 應用在一個不在存活會話上調用一個操做
  2. ZooKeeper客戶端和一個服務端斷開鏈接當還有後續的操做做用到這個服務端,也就是說還有後續的異步調用。

3.2.0新加 -- SessionMovedException.有一個內部的異常叫作SessionMovedException,一般不被客戶端看到。這個異常發生由於在一個鏈接中收到一個請求,這個會話已經被鏈接到一個不一樣的服務端。這個錯誤的一般緣由是一個服務端發送一個請求到服務端,可是網絡包有延遲,全部客戶端超時而且鏈接到一個新的服務端。當延遲的數據包到達了第一個服務端,老的服務端檢測到這個會話已經移動 了,而且關閉客戶端鏈接。客戶端一般不會看到這個錯誤由於他們不會從這個老的鏈接中讀數據(老的鏈接一般已經關閉)。這個條件能夠被看到的狀況是當兩個客戶端試圖從新創建相同鏈接用一個保存的會話id和密碼。其中一個客戶端將從新創建鏈接而另外一個將被斷開鏈接(致使試圖從新鏈接它的會話的對無期限地)

Updating the list of servers 咱們容許一個客戶端經過一個新的逗號分隔的host:port列表對來更新鏈接字符串,這每個對應一個ZooKeeper服務端。這個功能調用 一個機率的負載均衡邏輯,這個邏輯可能引發客戶端和它的當前主機斷開鏈接,以便達到新列表的每個服務端都有均一鏈接數。若是當前鏈接的主機不在新的列表裏面,那麼此次調用將老是引發這個鏈接被丟棄。不然,這個決定是基因而否服務端的數量是增長了仍是減少了和增長減少了多少。

例如,若是選擇的鏈接字符串包括3個主機而且如今 的列表包含3個主機和2個新主機。3個主機中的每個主機的40%將轉移到新主機中的一臺爲了平衡壓力。這個邏輯將致使這個客戶端有40%的機率丟掉當前鏈接的主機,而且在這個例子當中這個客戶端鏈接到2個新主機中的一個,隨機選擇。

另外一個例子 -- 假如咱們有5個主機,如今更新這個列表來刪除其中兩個主機,剩下的3個主機的鏈接仍然保持鏈接,而後全部鏈接到刪除的兩個主機的鏈接將須要移動到剩下3臺中的一臺,隨機選擇。若是鏈接被丟掉,客戶端移動到一個特殊的模式,它利用機率算法來選擇一個新的主機,而不只僅是輪詢。

在第一個例子中,第一個客戶端有40%的機率決定斷掉鏈接,若是這個決定肯定,它將試圖隨機鏈接一個新主機,而且只有它不能鏈接到任何一個新主機的時候,它將試圖鏈接老的主機。在找到一個服務端以後,或者嘗試了全部新列表中的服務端以後而且鏈接失敗,客戶端返回普通操做模式,它從鏈接字符串選擇任一一個服務端而且嘗試鏈接它。若是失敗,它將輪訓地嘗試不一樣的主機。(參考上面開始選擇服務端的邏輯)

ZooKeeper Watches

全部ZooKeeper中的讀操做 - getData(),getChildren(),和exist() - 有一個設置監視器選項。這裏是ZooKeeper的監視器的定義:一個監視器事件是一次性觸發,發送給設置這個監視器的客戶端,這個事件當設置監視器的數據發生改變的時候發生。監視器的定義有三個關鍵點須要考慮:

  • 一次性觸發

一個監聽事件將會被髮給客戶端在數據已經改變的時候。例如,若是一個客戶端作了一個getData("/znode1", true)操做,而後/znode1的數據被改變或刪除,客戶端將等到一個/znode1的監聽事件。若是/znode1再次改變,將沒有監聽事件被髮生,除非客戶端作了另外一個讀操做而且設置 一個新的監視器。

  • 發送給客戶端

這意爲着一個事件正在發送給客戶端的路上,可是可能尚未到達客戶端在成功返回以前改變操做到達客戶端以前。監視器被異步地發送給監聽聽。ZooKeper提供了一個順序保證:一個客戶端將不會看到它設置監視器的數據的改變直接它看到了監視事件。網絡延遲或其它因素可能致使不一樣看到監視器而且返回代碼在不一樣的時候點。關鍵點是不一樣的客戶端看到的任務東西都是有順序的。

  • 設置監視器的數據

這是指一個節點能夠以不一樣的方式改變。它有助於把ZooKeeper想像成一個維護兩個監視器的列表:數據監視器和孩子監視器。getData()和exists()設置數據監視器。getChildren()設置孩子監視器。另外,它可能有幫助的根據數據返回的類型想像 正在設置的監視器。getData()和exixts()返回關於這個節點的信息,然而getChildred()返回一個孩子的列表。所以,setData()將觸發給znode設置的數據監視器(假設成功設置)。一個成功的create()將觸發正在被建立的這個znode的數據監視器,父節點的孩子監視器。一個成功的delete()將觸發正在被刪除的znode的數據監視器和孩子監視器(由於沒有了孩子節點)同時也觸發這個被刪除節點的父節點的孩子觸發器。

監視器被維護在客戶端鏈接的ZooKeeper服務端的本地。這使監視器設置,維護,和分發都很輕量。當一個客戶端鏈接到一個新的服務端,監視器將會觸發任何會話事件。監視器將不會被收到當正在和服務端處於斷開狀態。當一個客戶端從新鏈接,先前註冊的監視器將被從新註冊若是須要被觸發。一般這些透明地發生。有一種狀況一個監視器可能丟失:尚未建立的znode的存在性的監視器將被丟失若是znode被建立而且刪除在處於斷開鏈接的時候。

監視器的語義

咱們有三個讀取ZooKeeper狀態的的調用能夠設置監視器:exists(),getData(),和getChildren().下面的列表詳細說明了一個監視器能夠觸發的事件和能夠觸發的調用:

  • 建立事件:

調用exists()時候觸發。

  • 刪除事件:

調用exists(),getData(),和getChildren()的時候觸發。

  • 改變事件:

調用exists()和getData()的時候觸發

  • 孩子事件:

調用getChildren的時候觸發

刪除監視器

咱們可能調用removeWatches來刪除註冊到一個znode的監視器。同時,ZooKeeper客戶端能夠在本地刪除監視器即便沒有服務器跟它鏈接經過設置本地標誌爲true.下面的列表詳細描述了將被觸發的事件在成功刪除監視器以後。

  • 孩子刪除事件

調用getChildren增長的監視器

  • 數據刪除事件

調用exists或getData增長的監視器

有關Watches的ZooKeeper擔保

關於監視器,ZooKeeper維護三個擔保

  • 監視器被排序和其它事件,其它監視器和異步回覆。ZooKeeper客戶端庫確保分發的全部事件 都是有序的。
  • 一個客戶端將看到它正在監視的znode的監視事件在它看到這個znode的新整數以前。
  • ZooKeeper的監視事件的順序和被ZooKeeper服務端的看到的更新順序一一對應。

關於監視器須要記住的事

  • 監視器是一次性觸發器;若是你獲得一個監聽事件,而且你想在之後的改變時被通知,你必須設置另外一個監視器。
  • 由於監視器是一次性觸發器而且在獲取事件和發送新請求來獲取一個監視器以前有延遲。你不能可靠地看到ZooKeeper中的znode的每個改變。請準備處理在獲取事件和設置再次設置監視器之間znode改變不少次的狀況。(你能夠不關心,但最少知道它可能發生)。
  • 一個監視器對象,或者函數/上下文對,對於一個給定的通知將會被觸發一次。例如,若是給相同的文件的exists和getData調用設置相同的監視器對象,而且這個文件隨後被刪除,那麼這個監視器對象將只被觸發一次這個文件刪除的通知。
  • 當你和一個服務端斷開鏈接(例如,當服務端失敗),你將不會獲得 任何監視器只到鏈接被從新創建。由於這個緣由會話事件被髮送給全部未完成的監視器處理器。使用會話事件進入一個安全模式:你將不會收到事件在斷開鏈接的時候,因此你的進程在這個模式下應該保守地採起行動。

使用ACLs的ZooKeeper的訪問控制

ZooKeeper使用ACLs來控制對znodes的訪問(ZooKeeper的數據樹的數據節點)。ACL的實現和UNIX文件訪問權限很是類似:它用權限位來容許/不容許對應節點的不一樣的操做和權限位應用的範圍。和標準的NUIX權限不一樣的是,ZooKeeper節點沒有對用戶,組,傲世界(其它的)的三個標準的範圍限制(文件的擁有者)。ZooKeeper沒有znode擁有者這種概念。相反地,ACL指定了一組id和這些id關聯的權限。

也注意ACL只適用一些特定的znode.特別地它沒有應用到孩子。例如,若是/app 只對ip:172.16.16.1可讀而且/app/status是全局可讀的,任何人將能夠讀取/app/status;ACL是不可遞歸的。

ZooKeeper支持可插拔的認證方案。Ids被以cheme:id的形式指定,這裏scheme是id對象的認證方案。例如, ip:172.16.16.1是主機172.16.16.1的id.

當客戶端鏈接到一個ZooKeeperu而且認證了它本身,ZooKeeper把客戶端鏈接關聯到這個客戶端對應的id上。這個ids根據znode的ACLs被檢查當客戶端試圖訪問一個節點的時候。ACLs被以成對的(scheme:expression, perms)組成。expression的格式對scheme來講是特定的。例如,(ip:19.22.0.0/16, READ)對任何ip地址以19.22開頭的客戶端有讀權限。

ACL權限

ZooKeeper支持以下的權限:

  • CREATE:你能夠建立一個節點
  • READ:你能夠從這個節點和這個節點的孩子列表獲取數據
  • WRITE:你能夠設置一個節點的數據
  • DELETE:你能夠刪除這個節點
  • ADMIN:你能夠設置權限

對於細粒度的訪問控制CREATE和DELETE權限已經被WRITE權限給打破。下面是CREATE和DELETE的例子:

你但願A能夠設置ZooKeeper節點的值,可是不能建立或刪除子節點。

沒有DELETE的CREATE:客戶端建立請求經過在父節點建立ZooKeeper節點。你想全部的客戶端能夠增長,可是隻有請求進程能夠刪除。(這就好像文件的APPEND權限)

同時,ADMIN權限在這裏是由於ZooKeeper沒有一個文件全部者的概念。在某種意義上ADMIN權限指明瞭擁有者。ZooKeeper不支持LOOKUP權限(在目錄上的執行權限位容許你LOOKUP即便你不能羅列這個目錄)。每個人顯式地擁有LOOKUP權限。這容許你一個節點,可是不會有更多。(問題是,若是你想在不存在的節點上調用zoo_exists(),沒有任何權限檢查)

內置的ACL方案

ZooKeeper有如下內置的方案:

world有一個單獨的id,anyone,這表明任何人。

auth 不使用任何id,表示任何受權的用戶

digest 使用一個 username:password字符串來生成 一個MD5哈希。而後被用來做爲ACL ID標示。受權經過發送一個明文username:password來完成。當在ACL中使用這個表達式將會是username:base64 encoded SHA1 password digest

ip 用客戶端的主機ip做爲ACL ID標示。ACl表達式addr/bits,這裏的addr的前bits位和客戶端主機ip的前bits位來匹配。

-----------------------------------------------------這裏有一段關於c語言的部分被省略了--------------------------------------------------------------------

可插拔的ZooKeeper認證

ZooKeeper運行不一樣的認證方案的不一樣的環境中,因此它有一個徹底可插拔的認證框架。甚至內置的認證方案也是用的這個可插拔的認證框架。

爲了理解這個認證框架是怎麼工做的,首先你必須理解兩個主要的認證操做。框架首先必需認證客戶端。這個一般一旦客戶端鏈接服務端的時候被完成而且包含從客戶端發送的校驗信息而且把這些和鏈接關聯起來。第二個被框架處理的操做是在ACL中找一個對應這個客戶端的條目。ACL條目是<idspec, permissions> 對。idspec多是一個簡單的string和匹配這個鏈接關聯的認證信息或者它多是一對這個信息計算的表達式。這取決於認證插件作這個匹配的具體實現。

這裏是認證插件必須實現的接口:

 

public interface AuthenticationProvider {
    String getScheme();
    KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]);
    boolean isValid(String id);
    boolean matches(String id, String aclExpr);
    boolean isAuthenticated();
}

 

第一個方法getScheme返回標示這個插件的字符串。由於 咱們直接多種認證的方法,一個認證證書或idspec將老是以scheme爲前綴。ZooKeeper服務端使用經過認證插件返回的scheme來決定scheme應用到那一個id。

handleAuthentication被調用當一個客戶端發送被關聯到這個鏈接的認證信息時。客戶端指定這個信息對應的scheme.ZooKeeper服務端傳遞這個信息給認證插件。插件 getScheme匹配被客戶端傳過來的scheme。handleAuthentication的實現者一般將返回一個錯誤若是它肯定信息是壞的,或者它將把這個信息和這個鏈接關聯起來使用cnxn.getAuthInfo().add(new Id(getScheme(), data))

認證插件被參與到設置和使用ACLs,當一個ACL被設置給一個znode,ZooKeeper服務端將傳遞條目的id部分給 isValid(String id) 方法。它取決於插件來驗證id是不是一個正確的形式。例如,ip:172.16.0.0/16 是一個合法的id,可是ip:host.com不是。若是新ACL包含一個"auth"條目,isAuthenticated 被用來查看是否跟這個鏈接關聯的這個scheme的認證信息應該被加到這個ACL。一些schemes不該該包含在auth。例如,客戶端的IP地址不被認爲應該被做爲id加入到ACL若是auth被指定。

ZooKeeper在檢查ACL的時候調用matches(String id, String aclExpr)。它須要這個客戶端的認證信息和相關的ACL條目匹配。爲了找一個應用這個客戶端了條目,ZooKeeper服務端將找到每個條目的scheme而且若是有這個scheme的認證信息,matches(String id, String aclExpr)將被調用,而後id賦值給先前被handleAuthentication和aclExpr設置ACL條件的id被加入到鏈接的認證信息。認證插件使用它本身的邏輯而且匹配scheme來決定是否id被包含在aclExpr。

這裏有兩個內置的認證插件:ipdigest。其它的插件可使用系統屬性被加入。在啓動時ZooKeeper服務端將尋找以"zookeeper.authProvider."開頭的系統屬性而且解析這些屬性的值爲認證插件的類名。這個屬性可使用 -Dzookeeeper.authProvider.X=com.f.MyAuth或者像下面同樣在服務端的配置文件裏增長一個條目被設置:

authProvider.1=com.f.MyAuth
authProvider.2=com.f.MyAuth2

應該注意來保證屬性的後綴是惟一的。若是是重複的例如 -Dzookeeeper.authProvider.X=com.f.MyAuth -Dzookeeper.authProvider.X=com.f.MyAuth2。只有一個將被使用。同時全部的服務端必須有相同的插件定義,不然客戶端使用插件提供的認證方案將會有問題。

一致性保證

ZooKeeper是一個高性能,可擴展的服務。讀和寫操做都被設計得很快。這的緣由是在讀取的狀況下,ZooKeeper能夠服務老的數據,反過來也是由於ZooKeeper的一致性保證:

順序的一致性

從客戶端來的更新將被按照它們發送的順序應用。

原子性

更新要麼成功要麼失敗 -- 沒有部分結果。

單系統鏡像

客戶端將會看到服務端相同的視圖無論它鏈接的是那一個服務端

可靠性

一旦更新被應用,它將會被留存到直到一個客戶端覆蓋這個更新。這個擔保有兩個結果:

  1. 若是客戶端獲得 一個成功的返回結果,這個更新已經被應用。在一些失敗(通訊錯誤,超時,等等)客戶端將不會知道是否更新被應用。咱們採起措施來減少這種失敗,可是這擔保是在成功返回的時候出現。(在Paxos中這被稱爲單調性條件)。
  2. 任務被客戶端看到的更新,帶過讀請求或成功更新,將永遠不會被回滾當從服務端失敗恢復過來的時候。

時效性

系統的客戶端視圖在必定的時限內(幾十秒的順序)保證是最新的。要麼系統改變將被客戶端看到,要麼客戶端將檢測到服務端過時。

利用這些一致性保證,很是容易構建高級別的功能,例如領導者選舉,屏障,隊列和讀寫可撤銷鎖在ZooKeeper客戶端(對ZooKeeper來講不須要更多)。更詳細的信息請參考Recipes and Solutions

注意

有時候開發者錯誤地認爲另外一個保證ZooKeeper沒有實現。它就是:

同時一致的跨客戶端視圖

ZooKeeper沒有及時地在每個實例上作保證,兩個不一樣的客戶端將有ZooKeeper數據一致的視圖。由於像網絡延遲之樣的因素,一個客戶端可能在另外一個客戶端獲取這個改變以前執行一個更新。考慮有兩個客戶端A和B的場景。若是客戶端A設置節點/a的值從0到1,而後告訴客戶端B讀取/a,客戶端B可能讀到老的數據0,取決於它鏈接是那一個服務端。若是客戶端A和B讀到相同的值很是重要,客戶端B應該調用sync()方法在它執行讀操做以前。

因此,ZooKeeper自己不保證改變在全部的服務端兩步發生,可是ZooKeeper原語可能被用來構造更高級的功能來提供有用的客戶端同步。(更多信息請參考ZooKeeper Recipes)。

綁定(Bindings)

ZooKeeper客戶端庫由兩種語言:Java和C.下面的部分描述這些內容。

Java Binding

ZooKeeper的Java bingding有兩個包:org.apache.zookeeper 和 org.apache.zookeeper.data。組成ZooKeeper的剩下的其它包被用來在內部使用或是服務端實現的一部分。org.apache.zookeeper.data包被生成的classes組成,這些類被簡單地用做容器。

被ZooKeeper Java客戶端使用的最主要類是ZooKeeper類。它的兩個構造器只是在可選的session id和password上不一樣。ZooKeeper支持在整個進程實例中會話恢復。Java程序能夠保存它的session id和password到一個徹底的存儲設備上,重啓,而且恢復這個被先前程序的實現使用的會話。

當一個ZooKeeper對象被建立,兩個線程也被建立:一個IO線程和一個事件線程。全部IO操做都在IO線程(NIO)上發生。全部事件調用發生在事件線程上。例如到ZooKeeper服務端的重連的會話維護和維護心跳是在IO線程上完成。同步方法的回覆也是在IO線程中處理。全部異步方法的回覆和監聽事件在事件線程中處理。這種設計有一些事情須要注意:

  • 全部異步調用的完成和監視器回調將被按順序完成。一次一個。調用者能夠作它想作的任何處理,可是在這期間沒有其它回調將被處理。
  • 回調不會阻塞IO線程的處理或者同步調用的處理。
  • 同步調用可能不以正確的順序返回。例如,假如一個客戶端這個以下的處理:發起一個對節點/a的異步讀而且設置watch爲true,而後在這個讀完成的回調中它作了一個同步的/a讀。(可能不是一個好的實踐,可是也不非法,而且它只是一個簡單的例子)。注意若是在異步讀和同步讀之間有一個改變,客戶端庫將會收到一個監視器事件說/a被改變在同步讀以前,可是由於完成回調正在阻塞事件隊列,同步的讀將會返回/a的新值在監視器事件被處理以前。

最後,與關閉相關的法則是直接的:一旦一個ZooKeeper對象被關閉或者收到一個導致的事件(SESSION_EXPIRED 和 AUTH_FAILED),ZooKeeper對象變得無效。在關閉時,兩個線程關閉而且任何對ZooKeeper的進一步的訪問是未定義的行爲而且應該被避免。

-------------------------------------有關C的內容被忽略---------------------------------------------

 

Building Blocks: ZooKeeper操做指南

本節調查一個開發人員能夠對ZooKeeper服務端執行的全部操做。這相比前面的概念章節是比較低級別的信息,可是比ZooKeeper API手冊高級一些。它包含這些主題:

  • Connecting to ZooKeeper

錯誤處理

Java和C客戶端綁定均可能報告錯誤。Java客戶端綁定經過拋出KeeperExecption來報告錯誤,調用異常的code()方法將返回指定的錯誤代碼。C客戶端綁定返回一個ZOO_ERRORS枚舉的錯誤代碼。API回調錶示兩種語言綁定的返回代碼。更多信息請參考API文檔關於可能的錯誤和它們的意思。

Connecting to ZooKeeper

Read Operations

Write Operations

Handling Watches

Miscelleaneous ZooKeeper Operations

Program Structure, with Simple Example

陷阱:常見問題和故障排除

如今你瞭解了ZooKeeper。它使你的應用很快,簡單,可是等等。。。有一點問題。這裏有一些ZooKeeper用戶可能要掉進去的陷阱:

  1. 若是你正使用監視器,你必需尋找鏈接的監聽事件。當一個ZooKeepe客戶端和一個服務端斷開,你將不會收到這一改變的通知直到你從新鏈接。若是你正監視一個znode是否存在,你將錯過這一事件若是znode在你斷開鏈接的時候被創始和刪除。
  2. 你必需測試ZooKeeper服務端失敗。ZooKeeper服務端可能從失效中存活只到大多數的服務端是活躍的。要問的問題是:你的應用程序能夠處理它麼?在真實世界一個客戶端到ZooKeeper的鏈接可能斷掉。(ZooKeeper服務端失效和網絡隔離是鏈接丟失的常見緣由)ZooKeeper客戶端庫負責處理你的鏈接恢復和讓你知道發生了什麼 事,可是你必須確保你恢復了你的狀態和任何失敗的沒有處理的請求。在測試 環境中找到是否你是對的,而不是在生產環境 - 測試由一些服務端組成的ZooKeeper服務而且重啓他們。
  3. 被客戶端使用的ZooKeeper服務端列表必須和每個ZooKeeper服務端擁有的ZooKeeper服務端列表匹配。若是客戶端列表是真實ZooKeeper服務端列表的子集,事件能夠工做,儘管不是太完美。可是不能是客戶端的ZooKeeper服務端列表不在ZooKeeper集羣裏。
  4. 關於你在那裏存放事務日誌應該當心。ZooKeeper的性能最關鍵部分是事務日誌。ZooKeeper必須在返回響應以前同步事務到一個媒介中。一個專門的事務日誌設備是一向性能良好的關鍵。把日誌放在一個比較繁忙的設置上將會影響性能。若是你只有一個存儲設備,把日誌文件放在NFS而且增長快照數量;它沒有消除這個問題,可是它能減輕它。
  5. 正確地設置你的Java最大堆的值。避免交換是很是重要的。沒必要要的進入磁盤將幾乎確定地下降你的性能。記住,在ZooKeeper中,全部事件都是有序的,若是若是一個請求點擊磁盤,全部的其它請求也點南磁盤。爲了不磁盤交換,嘗試設置你擁有的物理內在的數量,再減去操做系統和緩存須要的值。最好的決定最優的堆大小的方法是運行壓力測試。若是由於一些緣由你不能運行壓力測試,保守估計而且選擇一個低於引發你機器交換的值。例如,在一個4G內在的機器上,3G堆大小是一個保守的估計。
相關文章
相關標籤/搜索