ZooKeeper開發者指南(五)


引言

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

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

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

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

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

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

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堆大小是一個保守的估計。

正式文檔除外,有一些ZooKeeper開發者的其它的信息資源。

ZooKeeepr白板[tbd:find url]

Yahoo! Research的ZooKeeper設計和性能的最終討論

API Reference [tbd: find url]

ZooKeeeper API的完成參考手冊

ZooKeeper Talk at the Hadoup Summit 2008

A video introduction to ZooKeeper, by Benjamin Reed of Yahoo! Research

Barrier and Queue Tutorial

Flavio Junqueira的相當好的Java指南,使用ZooKeeper實現簡單的屏障和生產-消費者隊列

ZooKeeper - A Reliable, Scalable Distributed Coordination System

An article by Todd Hoff (07/15/2008)

ZooKeeper Recipes

Pseudo-level discussion of the implementation of various synchronization solutions with ZooKeeper: Event Handles, Queues, Locks, and Two-phase Commits.

[tbd]

Any other good sources anyone can think of...

 

  插播個廣告 


老丈人家的粉皮兒,農產品,沒有亂七八糟的添加劑,歡迎惠顧
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM