ZooKeeper程序員指南


原文地址 http://blog.sina.com.cn/s/blog_56dee71a0100wcvu.html

譯自http://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html

1 簡介

本文是爲想要創建使用ZooKeeper協調服務優勢的分佈式應用的開發者準備的。本文包含理論信息和實踐信息。

本指南的前四節對各種ZooKeeper概念進行較高層次的討論。這些概念對於理解ZooKeeper是如何工作的,以及如何使用ZooKeeper來進行工作都是必要的。這幾節沒有代碼,但卻要求讀者對分佈式計算相關的問題較爲熟悉。這四節是:

l         ZooKeeper數據模型

l         ZooKeeper會話

l         ZooKeeper觀察

l         一致性保證

接下來的四節提供了實際編程的信息。這四節是:

l         創建塊:ZooKeeper操作指南

l         綁定

l         程序結構和簡單示例

l         轉向:常見問題和解決

本文最後的附錄包含到其他有用的ZooKeeper相關信息的鏈接。

本文的大多數信息以可獨立訪問的參考材料的形式存在。但是,在編寫第一個ZooKeeper應用程序之前,你應該至少讀過ZooKeeper數據模型ZooKeeper基本操作。此外,簡單示例程序也有助於理解ZooKeeper客戶端應用程序的基本結構。

2 ZooKeeper數據模型

ZooKeeper有一個分層的名字空間,跟分佈式文件系統很相似。唯一的不同是,名字空間中的每個節點都可以有關聯的數據和子節點。這就像一個允許文件也是目錄的文件系統。節點路徑總是表達爲規則的、斜槓分隔的絕對路徑,不存在相對路徑。路徑可以使用任何Unicode字符,但是需要遵循下列限制:

l         不能使用空字符(\u0000)。(這在C綁定中會導致問題)

l         因爲不能正確顯示,或者容易弄混淆,不能使用這些字符:\u0001 - \u0019\u007F - \u009F

l         不允許使用這些字符:\ud800 - uF8FFF\uFFF0 - uFFFF\uXFFFE - \uXFFFF(X1E之間的一個數字)\uF0000 - \uFFFFF

l         可以使用小數點,但是不能單獨使用...來指示路徑中的節點,因爲ZooKeeper不使用相對路徑。/a/b/./c或者/a/b/../c是無效的。

l         記號zookeeper是保留的。

2.1 ZNode

ZooKeeper樹中的節點稱作znodeznode會維護一個包含數據修改和ACL修改版本號的Stat結構體,這個結構體還包含時間戳字段。版本號和時間戳讓ZooKeeper可以校驗緩存,協調更新。每次修改znode數據的時候,版本號會增加。客戶端獲取數據的同時,也會取得數據的版本號。執行更新或者刪除操作時,客戶端必須提供版本號。如果提供的版本號與數據的實際版本不匹配,則更新操作失敗。(可以覆蓋這個行爲,更多信息請看……)

注意:

分佈式應用工程中,node這個詞可以指代主機、服務器、集羣成員、客戶端進程等等。ZooKeeper文檔用znode指代數據節點;用server指代組成ZooKeeper服務的機器;用quorum peer指代組成集羣的服務器;用client指代任何使用ZooKeeper服務的主機或者進程。

znode是程序員訪問的主要實體,它有一些值得討論的特徵。

2.1.1 觀察

客戶端可以在znode上設置觀察。對znode的修改將觸發觀察,然後移除觀察。觀察被觸發時,ZooKeeper向客戶端發送一個通知。關於觀察的更多信息請看ZooKeeper觀察

2.1.2 數據存取

存儲在名字空間中每個znode節點裏的數據是原子地讀取和寫入的。讀取操作獲取節點的所有數據,寫入操作替換所有數據。節點的訪問控制列表(ACL)控制可以進行操作的用戶。

ZooKeeper不是設計用來作爲通用數據庫或者大型對象存儲的,而是用來存儲協調數據的。協調數據的形式可能是配置、狀態信息、聚合等等。各種形式的協調數據的一個共同特點是:它們通常比較小,以千字節來衡量。ZooKeeper客戶端和服務器實現會進行檢查,以保證znode數據小於1MB,但是平均的實際數據量應該遠小於1MB。對較大數據的操作將導致某些操作比其他操作耗費更多時間,進而影響某些操作的延遲,因爲需要額外的時間在網絡和存儲媒體間移動更多數據。如果需要大數據存儲,通常方式是存儲到塊存儲系統,如NFS或者HDFS中,然後在ZooKeeper中保存到存儲位置的指針。

2.1.3 臨時節點

ZooKeeper有臨時節點的概念。臨時節點在創建它的會話活動期間存在。會話終止的時候,臨時節點被刪除,所以臨時節點不能有子節點。

2.1.4 順序節點:唯一命名

創建znode時,可以要求ZooKeeper在路徑名後增加一個單調增加的計數器部分。這個計數器相對於znode的父節點是唯一的。計數器的格式是0d,也就是帶有0填充的10個數字(這種格式是爲了方便排序),比如說,<path>0000000001隊列接收節裏有一個使用這種特徵的例子。注意:用於存儲下一個順序號的計數器是一個由父節點維護的有符號整數(4字節),所以計數器將在超過2147483647的時候溢出(導致名字成爲<path>-2147483647)。

2.2 ZooKeeper中的時間

ZooKeeper以多種方式跟蹤時間:

l         zxid

每次修改ZooKeeper狀態都會收到一個zxid形式的時間戳,也就是ZooKeeper事務ID。事務IDZooKeeper中所有修改總的次序。每個修改都有唯一的zxid,如果zxid1小於zxid2,那麼zxid1zxid2之前發生。

l         版本號

對節點的每次修改將使得節點的版本號增加一。版本號有三種:versionznode數據修改的次數)、cversionznode子節點修改的次數),以及aversionznodeACL修改次數)。

l         tick

多服務器ZooKeeper中,服務器使用tick來定義狀態上傳、會話超時、節點間連接超時等事件的時序。tick僅被最小會話超時(2倍的tick時間)間接使用:如果客戶端要求小於最小會話超時的時間,服務器將告知客戶端,實際使用的是最小會話超時。

l         真實時間

除了在創建和修改znode時將時間戳放入stat結構體中之外,ZooKeeper不使用真實時間,或者說時鐘時間。

2.3 ZooKeeperStat結構體

ZooKeeper中每個znodeStat結構體由下述字段構成:

l         czxid:創建節點的事務的zxid

l         mzxid:對znode最近修改的zxid

l         ctime:以距離時間原點(epoch)的毫秒數表示的znode創建時間

l         mtime:以距離時間原點(epoch)的毫秒數表示的znode最近修改時間

l         versionznode數據的修改次數

l         cversionznode子節點修改次數

l         aversionznodeACL修改次數

l         ephemeralOwner:如果znode是臨時節點,則指示節點所有者的會話ID;如果不是臨時節點,則爲零。

l         dataLengthznode數據長度。

l         numChildrenznode子節點個數。

3 ZooKeeper會話

客戶端使用某種語言綁定創建一個到服務的句柄時,就建立了一個ZooKeeper會話。會話創建後,句柄處於CONNECTING狀態,客戶端庫會試圖連接到組成ZooKeeper服務的某個服務器;連接成功則進入到CONNECTED狀態。通常操作中句柄將處於這兩個狀態之一。如果發生不可恢復的錯誤,如會話過期、身份鑑定失敗,或者應用顯式關閉,則句柄進入到CLOSED狀態。下圖顯式了ZooKeeper客戶端可能的狀態轉換:

 

要創建客戶端會話,應用程序代碼必須提供一個包含逗號分隔的列表的字符串,其中每個主機:端口對代表一個ZooKeeper服務器(例如,"127.0.0.1:4545"或者"127.0.0.1:3001,127.0.0.1:3002")。ZooKeeper客戶端庫將試圖連接到任意選擇的一個服務器。如果連接失敗,或者到服務器的連接斷開,則客戶端將自動嘗試連接到列表中的下一個服務器,直到連接(重新)建立。

3.2.0版新增加:可以在連接字符串後增加可選的"chroot"後綴,這讓客戶端命令都是相對於指定的根的(類似於Unixchroot命令)。例如,如果使用"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,所有路徑將是相對於這個根的:獲取/設置/foo/bar數據的操作將實際在/app/a/foo/bar上執行(從服務器來看)。這個特徵在多用戶環境中特別有用,某個特定ZooKeeper服務的每個用戶可以使用不同的根。這讓重用更加簡單,用戶應用在編碼時以/爲根,但實際的根位置(如/app/a)可以在部署時確定。

客戶端取得ZooKeeper服務句柄時,ZooKeeper創建一個會話,由一個64位數標識,這個數將返回給客戶端。如果連接到其他服務器,客戶端將在連接握手時發送會話ID。出於安全考慮,服務器會爲會話ID創建一個密碼,ZooKeeper服務器可以校驗這個密碼。這個密碼將在創建會話時與會話ID一同發送給客戶端。與新的服務器重新建立會話的時候,客戶端會和會話ID一同發送這個密碼。

客戶端庫創建會話時需要的參數之一是毫秒錶示的會話超時。客戶端發送請求的超時值,服務器以可以分配給客戶端的超時值迴應。當前實現要求超時值最小是2倍的tickTime(在服務器配置文件中設置),最大是20倍的tickTime。客戶端API可以獲取商定的超時值。

從服務集羣分裂開來時,客戶端(會話)將搜索會話創建時給出的服務器列表。最終,客戶端至少和一個服務器重新建立連接,會話再次進入“已連接”狀態(如果在會話超時之前重新連接上),或者進入到“已過期”狀態(如果在會話超時後才重新連接上)。不建議在斷開連接時創建一個新的會話對象(即一個新的ZooKeeper.class對象,或者C綁定中的zookeeper句柄),因爲客戶端庫會進行重新連接。特別是客戶端庫具有試探特徵,可以處理“羊羣效應”等問題。只需要在被通知會話已過期時創建新的會話(必須的)。

會話過期由ZooKeeper集羣,而不是客戶端來管理。客戶端與集羣建立會話時會提供上面討論的超時值。集羣使用這個值來確定客戶端會話何時過期。集羣在指定的超時時間內沒有得到客戶端的消息時發生會話過期。會話過期時集羣將刪除會話的所有臨時節點,立即通知所有(觀察節點的)客戶端。此時已過期會話的客戶端還是同集羣斷開連接的,不會被通知會話已經過期,直到(除非)客戶端重新建立到集羣的連接,這時候已過期會話的觀察纔會收到“會話已過期”通知。

已過期會話的觀察看到的狀態轉換過程示例:

1.已連接:會話建立,客戶端與集羣通信中(客戶端/服務器通信正常進行)

2.客戶端從集羣中分離

3.連接已斷開:客戶端失去同集羣的連接

4.時間流逝,超時時間過後,集羣讓會話過期,客戶端並不知道,因爲它還是同集羣斷開連接的。

5.時間流逝,客戶端與集羣間的網絡恢復正常。

6.已過期:最終客戶端重新連接到集羣,此時被通知會話已經過期。

 

建立會話時的另一個參數是默認觀察。客戶端發生狀態改變時觀察會被通知。比如說,客戶端將在失去同服務器的連接,或者會話過期時被通知。觀察應該認爲初始狀態是連接已經斷開(在客戶端庫向觀察發送任何狀態改變事件之前)。新建連接時發送給觀察的第一個事件通常是會話連接建立事件。

會話由客戶端發送的請求保持爲活動狀態。如果要空閒一段將導致超時的時間,客戶端將發送PING請求,保持會話是活動的。PING請求不僅讓服務器知道客戶端仍然是存活的,也讓客戶端可以確認,到服務器的連接依然是活動的。PING的時序足夠保守,確保能夠在合理的時間內檢測到死掉的連接,重新連接到新的服務器。

一旦到服務器的連接成功建立,則進行同步或者異步操作時,通常有兩種情況導致客戶端庫產生連接丟失事件(C綁定中的錯誤碼,Java中的異常:關於綁定特定的細節,請看API文檔):

1.應用程序對已經不存活/有效的會話進行操作

2.在有到服務器的未決操作(例如,有一個進行中的異步調用)時,客戶端斷開同服務器的連接

3.2.0版增加SessionMovedException。有一種稱作SessionMovedException的內部異常。通常客戶端看不到這個異常。在某連接上收到一個會話請求,但是這個會話已經重建到另一個服務器上的時候會發生這種異常。導致這種錯誤的原因通常是,客戶端向服務器發送請求,但是數據分組被延遲,以致客戶端超時並且連接到一個新的服務器。延遲的分組到達先前的服務器的時候,服務器檢測到會話已經移走,會關閉客戶端連接。客戶端通常看不到這個錯誤,因爲客戶端不會從較早的連接上讀取數據(通常關閉了較早的連接)。兩個客戶端試圖使用已保存的會話ID和密碼重新建立相同的連接時會看到這種錯誤。其中一個客戶端將重新建立連接,而另一個客戶端會被斷開連接(導致無限次地試圖重新建立連接/會話)。

4 ZooKeeper觀察

ZooKeeper中的所有讀操作:getData()getChildren()exists(),都有一個設置觀察作爲邊效應的選項。ZooKeeper對觀察的定義是:觀察事件是在被觀察數據發生變化時,發送給建立觀察的客戶端的一次性觸發器。對於這個定義,有三點值得關注:

l         一次觸發

觀察事件將在數據修改後發送給客戶端。比如說,如果客戶端執行getData("/znode1",true),然後/znode1的數據發生變化,或者被刪除,則客戶端將收到/znode1的觀察事件。如果再次修改/znode1,則不會給客戶端發送觀察事件,除非客戶端再執行一次讀取操作,設置新的觀察。

l         發送給客戶端

這暗示着,在(導致觀察事件被觸發的)修改操作的成功返回碼到達客戶端之前,事件可能在去往客戶端的路上,但是可能不會到達客戶端。觀察事件是異步地發送給觀察者(客戶端)的。ZooKeeper會保證次序:在收到觀察事件之前,客戶端不會看到已經爲之設置觀察的節點的改動。網絡延遲或者其他因素可能會讓不同的客戶端在不同的時間收到觀察事件和更新操作的返回碼。這裏的要點是:不同客戶端看到的事情都有一致的次序。

l         爲哪些數據設置觀察

節點有不同的改動方式。可以認爲ZooKeeper維護兩個觀察列表:數據觀察和子節點觀察。getData()exists()設置數據觀察。getChildren()設置子節點觀察。此外,還可以認爲不同的返回數據有不同的觀察。getData()exists()返回節點的數據,而getChildren()返回子節點列表。所以,setData()將爲znode觸發數據觀察。成功的create()將爲新創建的節點觸發數據觀察,爲其父節點觸發子節點觀察。成功的delete()將會爲被刪除的節點觸發數據觀察以及子節點觀察(因爲節點不能再有子節點了),爲其父節點觸發子節點觀察。

觀察維護在客戶端連接到的ZooKeeper服務器中。這讓觀察的設置、維護和分發是輕量級的。客戶端連接到新的服務器時,所有會話事件將被觸發。同服務器斷開連接期間不會收到觀察。客戶端重新連接時,如果需要,先前已經註冊的觀察將被重新註冊和觸發。通常這都是透明的。有一種情況下觀察事件將丟失:對還沒有創建的節點設置存在觀察,而在斷開連接期間創建節點,然後刪除。

4.1 ZooKeeper關於觀察的保證

l         觀察與其他事件、其他觀察和異步迴應是順序的。ZooKeeper客戶端庫保證一切都是按順序分發的。

l         客戶端將在看到znode的新數據之前收到其觀察事件。

l         觀察事件的次序與ZooKeeper服務看到的更新次序一致。

4.2 關於觀察需要記住的

l         觀察是一次觸發的:如果想在收到觀察事件之後收到未來修改的通知,必須再次設置觀察。

l         因爲觀察是一次觸發的,而收到觀察事件和發送新的請求、再次建立觀察之間是有延遲的,所以不能可靠地觀察到節點的所有修改。應該要準備處理在收到觀察事件和再次設置觀察之間,節點被多次修改的情況。(可以不處理,但至少要知道這種情況是可能的)

l         一個觀察對象,或者函數/上下文對,只會因爲某個通知而觸發一次。比如說,對同一個文件使用existsgetData調用,設置相同的觀察對象,然後文件被刪除,則觀察對象只會被調用一次,帶有文件刪除通知。

l         與服務器斷開連接期間(比如說,服務器故障)不能收到任何觀察事件,直到連接重新建立。因此,會話事件是發送給所有未決觀察處理器的。可使用會話事件進入到安全模式:斷開連接期間不會收到任何事件,進程應該謹慎操作。

5 使用ACL的訪問控制

ZooKeeper使用ACL控制對節點的訪問。ACL的實現同Unix文件訪問權限非常相似:採用權限位來定義允許/禁止的各種節點操作,以及位應用的範圍。與標準Unix權限不同的是,ZooKeeper節點不由用戶(文件所有者)、組和其他這三個標準範圍來限制。ZooKeeper沒有節點所有者的概念。取而代之的是,ACL指定一個ID集合,以及與這些ID相關聯的權限。

還要注意的是,ACL僅僅用於某特定節點。特別是,ACL不會應用到子節點。比如說,/app只能被ip:172.16.16.1讀取,/app/status可以被所有用戶讀取。ACL不是遞歸的。

ZooKeeper支持可插入式鑑權模式。使用scheme:id的形式指定ID,其中schemeid對應的鑑權模式。比如說,ip:172.16.16.1是地址爲172.16.16.1的主機的ID

客戶端連接到ZooKeeper,驗證自身的時候,ZooKeeper將所有對應客戶端的ID都關聯到客戶端連接上。客戶端試圖存取節點的時候,ZooKeeper會在節點的ACL中校驗這些IDACL(scheme:expression,perms)對組成。expression的格式是特定於scheme的。比如說,(ip:19.22.0.0/16,READ)給予任何IP地址以19.22開頭的客戶端以READ權限。

5.1 ACL權限

ZooKeeper支持下述權限:

l         CREATE:可創建子節點

l         READ:可獲取節點數據和子節點列表

l         WRITE:可設置節點數據

l         DELETE:可刪除子節點

l         ADMIN:可設置節點權限

WRITE權限中分離出CREATEDELETE可以取得更好的訪問控制。使用CREATEDELETE的情況:

l         希望A可以設置節點數據,但是不能CREATE或者DELETE子節點。

l         沒有DELETECREATE權限:客戶端通過在某父目錄中創建節點來創建請求。此時希望所有客戶端可以添加節點,但是隻有請求處理器可以刪除節點。(這與文件的APPEND權限類似)

此外,ADMIN權限存在的原因是,ZooKeeper沒有文件所有者的概念。某些情況下ADMIN權限可以指定實體的所有者。ZooKeeper不支持LOOKUP權限(目錄上的、允許進行LOOKUP的執行權限位,即使不能列出目錄內容)。每個用戶都隱含地擁有LOOKUP權限。這僅僅讓用戶可以取得節點狀態。(問題是,如果想對一個不存在的節點進行zoo_exists()調用,沒有權限可以檢查)

5.1.1 內置的ACL模式

ZooKeeper內置下述ACL模式:

l         world具有單獨的ID,代表任何用戶。

l         auth不使用任何ID,代表任何已確認用戶。

l         digest使用username:password字符串來生成MD5散列值,用作ID。身份驗證通過發送明文的username:password字符串來進行。用在ACL表達式中時將是username:base64編碼的SHA1密碼摘要。

l         ip使用客戶端主機IP作爲IDACL表達式的形式是addr/bits,表示addr的最高bits位將與客戶端主機IP的最高bits位進行匹配。

5.1.2 ZooKeeper C客戶端API

ZooKeeper C庫提供下述常量:

l         const int ZOO_PERM_READ;//可讀取節點的值,列出子節點

l         const int ZOO_PERM_WRITE;//可設置節點數據

l         const int ZOO_PERM_CREATE;//可創建子節點

l         const int ZOO_PERM_DELETE;//可刪除子節點

l         const int ZOO_PERM_ADMIN;//可執行set_acl()

l         const int ZOO_PERM_ALL;//OR連接的上述所有標誌

下面是標準的ACL ID

l         struct Id ZOO_ANYONE_ID_UNSAFE;//('world','anyone')

l         struct Id ZOO_AUTH_IDS;//('auth','')

空的ZOO_AUTH_IDS標識字符串應該解釋爲“創建者的標識”。

ZooKeeper有三種標準ACL

l         struct ACL_vector ZOO_OPEN_ACL_UNSAFE;//(ZOO_PERM_ALL,ZOO_ANYONE_ID_UNSAFE)

l         struct ACL_vector ZOO_READ_ACL_UNSAFE;//(ZOO_PERM_READ,ZOO_ANYONE_ID_UNSAFE)

l         struct ACL_vector ZOO_CREATOR_ALL_ACL;//(ZOO_PERM_ALL,ZOO_AUTH_IDS)

ZOO_OPEN_ACL_UNSAFE是完全開放自由的ACL:任何應用程序可以對節點進行任何操作,以及創建、列出和刪除子節點。ZOO_READ_ACL_UNSAFE給予任何應用程序以只讀訪問權限。CREATE_ALL_ACL給予節點創建者所有權限。創建者在使用這種ACL創建節點之前,必須已經通過服務器的身份鑑別(比如說,使用"digest"模式)。

下述ZooKeeper操作用於處理ACL

l         int zoo_add_auth(zhandle_t* zh,const char* scheme,const char* cert,int certLen,void_completion_t completion,const void* data);

應用程序使用zoo_add_auth函數向服務器驗證自身。如果想使用不同的模式和/或標識來進行身份驗證,可以多次調用這個函數。

l         int zoo_create(zhandle_t* zh,const char* path,const char* value,int valuelen,const struct ACL_vector* acl,int flags,char* realpath,int max_realpath_len);

zoo_create()創建新的節點。acl是與節點相關的ACL列表。父節點必須設置了CREATE權限位。

l         int zoo_get_acl(zhandle_t* zh,const char* path,struct ACL_vector* acl,struct Stat* stat);

這個函數返回節點的ACL信息。

l         int zoo_set_acl(zhandle_t* zh,const char* path,int version,const struct ACL_vector* acl);

這個函數替換節點的ACL列表。節點必須設置了ADMIN權限。

下面是一段使用上述API來進行foo模式的身份驗證,然後創建具有僅創建者可訪問權限的臨時節點/xyz的示例代碼。

注意

這是一個展示如何與ZooKeeper ACL交互的非常簡單的示例。更合適的C客戶端實現示例請看../trunk/src/c/src/cli.c

<……省略示例代碼……>

6 插入式身份驗證

ZooKeeper運行在各種使用不同身份驗證模式的環境中,所以它有一個完全插入式的身份驗證框架。內置的身份驗證模式也是使用這個框架的。

要理解身份驗證框架如何工作,首先必須理解兩種主要的身份驗證操作。框架首先要驗證客戶。這通常在客戶端連接到服務器後立即進行,由驗證客戶端發送的信息,或者驗證收集的關於客戶端的信息,並且將其關聯到連接兩個步驟構成。框架進行的第二個操作是在ACL中找出客戶端對應的實體。ACL實體就是<idspec,permissions>對。idspec可能是與連接關聯的身份驗證信息相匹配的簡單字符串,或者是一個可以計算出身份驗證信息的表達式。進行匹配是身份驗證插件要實現的任務。下面是身份驗證插件必須實現的接口:

 

第一個方法,getScheme返回標識插件的字符串。因爲支持多種身份驗證方法,所以每個身份驗證憑證,或者說idspec總是帶有scheme:前綴。ZooKeeper服務器使用身份驗證插件返回的模式字符串來確定將模式應用到哪些id

handleAuthentication在客戶端發送與連接相關聯的身份驗證信息時被調用。客戶端指定身份驗證信息的模式。ZooKeeper服務器將信息傳遞給getScheme返回值與客戶端傳遞的模式值相匹配的身份驗證插件。handleAuthentication通常在確定身份驗證信息不正確時返回錯誤,或者使用cnxn.getAuthInfo().add(new Id(getScheme(),data))將身份驗證信息關聯到連接。

身份驗證插件與設置和使用ACL相關。爲節點設置ACL時,ZooKeeper服務器會將條目的id部分傳遞給isValid(String id)方法。插件必須驗證id具有正確的形式。比如說,ip:172.16.0.0/16是一個有效的id,但是ip:host.com則不是。

如果新的ACL含有auth條目,則isAuthenticated用於確定與連接相關聯的身份驗證信息是否要添加到ACL中。某些模式不應該包含在auth中。比如說,如果指定了auth,則客戶端的IP地址不會被看作是id,不應該添加到ACL中。

檢查ACL時,ZooKeeper調用matches(String id,String aclExpr)。函數需要將客戶端的身份驗證信息與相應的ACL條目進行匹配。爲找出應用到客戶端的條目,ZooKeeper服務器找出每個條目的模式,如果有客戶端的、這個模式的身份驗證信息,則matches(String id,String aclExpr)會被調用,id設置爲先前通過handleAuthentication添加到連接的身份驗證信息,aclExpr設置爲ACL條目的id。身份驗證插件使用其邏輯進行匹配,確定id是否包含在aclExpr中。

有兩個內置的身份驗證插件:iddigest。可通過系統屬性添加額外的插件。ZooKeeper服務器啓動時會查找以zookeeper.authProvider.開頭的系統屬性,將這些屬性的值解釋爲身份驗證插件的類名。可使用-Dzookeeper.authProvider.X=com.f.MyAuth來設置這些屬性,或者在系統配置文件中添加類似於下面的條目:

 

應該注意,要確保後綴是唯一的。如果有重複的,如-Dzookeeper.authProvider.X=com.f.MyAuth-Dzookeeper.authProvider.X=com.f.MyAuth2,只會使用一個。此外,所有服務器必須定義有同樣的插件,否則客戶端使用插件提供的身份驗證模式連接到某些服務器時會有問題。

7 一致性保證

ZooKeeper高性能、可伸縮的服務。讀和寫操作都設計爲高速操作,雖然讀比寫更快。原因是在讀操作中,ZooKeeper可返回較老的數據,這源自ZooKeeper的一致性保證:

l         順序一致性:一個客戶端的更新將以發送的次序被應用。

l         原子性:更新要麼成功,要麼失敗,沒有部分結果。

l         單一系統鏡像:無論連接到哪個服務器,客戶端將看到同樣的視圖。

l         可靠性:一旦應用了某更新,結果將是持久的,直到客戶端覆蓋了更新。這個保證有兩個推論:

1.如果客戶端得到成功的返回碼,則更新已經被應用。某些失敗情況下(通信錯誤、超時等),客戶端不知道更新是否已經應用。我們採取措施保證最小化失敗,但這個保證只對成功的返回碼有效。(這稱作是Paxos中的單一條件)

2.服務器從失敗恢復時,客戶端通過讀請求或者成功更新看到的任何更新,都不會回滾。

l         及時性:保證客戶端的系統視圖在某個時間範圍(大約爲十幾秒)內是最新的。在此範圍內,客戶端要麼可看到系統的修改,要麼檢測到服務終止。

 

使用這些一致性保證,就可以很容易地單獨在ZooKeeper客戶端構建如領導者選舉、護欄、隊列以及可恢復的讀寫鎖等高層功能。更多細節請看Recipes and Solutions

注意:有時候開發者會錯誤地假定一個ZooKeeper實際上沒有提供的保證:

l         跨客戶端視圖的併發一致性

ZooKeeper並不保證在某時刻,兩個不同的客戶端具有一致的數據視圖。因爲網絡延遲的原因,一個客戶端可能在另一個客戶端得到修改通知之前進行更新。假定有兩個客戶端AB。如果客戶端A將一個節點/a的值從0修改爲1,然後通知客戶端B讀取/a,客戶端B讀取到的值可能還是0,這取決於它連接到了哪個服務器。如果客戶端AB讀取到相同的值很重要,那麼客戶端B應該在執行讀取之前調用sync()方法。

所以,ZooKeeper本身不保證修改在多個服務器間同步地發生,但是可以使用ZooKeeper原語來構建高層功能,提供有用的客戶端同步。(更多信息,請看ZooKeeper Recipes

8 綁定

ZooKeeper客戶端庫以兩種方式提供:JavaC。下面幾節描述這兩種綁定。

8.1 Java綁定

ZooKeeperJava綁定由兩個包組成:org.apache.zookeeperorg.apache.zookeeper.data。組成ZooKeeper的其他包由內部使用或者是服務器實現的組成部分。org.apache.zookeeper.data由簡單地用作容器的類構成。

ZooKeeper Java客戶端使用的主要類是ZooKeeper類。這個類的兩個構造函數的不同僅僅在於可選的會話ID和密碼。ZooKeeper支持進程的不同實例間的會話恢復。Java程序可以將會話ID和密碼保存到穩態存儲中,然後重啓、恢復程序先前實例使用的會話。

創建ZooKeeper對象的時候,會同時創建兩個線程:一個IO線程和一個事件線程。所有IOIO線程中發生(使用Java NIO)。所有事件回調則在事件線程中進行。重連到ZooKeeper服務器和維持心跳等會話維持活動在IO線程中進行。同步方法的迴應也在IO線程中進行。所有異步方法的迴應,以及觀察事件則在事件線程中處理。對於這個設計,有一些事情需要注意:

l         所有同步調用和觀察回調將按次序進行,一次一個。調用者可以進行任何想要的處理,但是在此期間不會處理其他回調。

l         回調不會阻塞IO線程或者同步調用的處理。

l         同步調用可能不會以正確的次序返回。比如說,假設客戶端進行下述處理:提交一個watch設置爲ture的、對節點/a的異步讀取,然後在讀取操作的完成回調中執行一個對/a的同步讀取。(可能是不好的實現,但是是合法的,這只是一個簡單的例子)

如果在異步讀取和同步讀取之間,對/a進行了修改,則客戶端庫將在同步讀取返回之前接收到一個事件,表明/a已經被修改。但是因爲完成回調阻塞了事件隊列,同步讀取將在觀察事件被處理之前返回/a的新值。

 

最後,關於關閉的規則很直接:一旦被關閉或者接收到致命事件(SESSION_EXPIREDAUTH_FAILED),ZooKeeper對象就變成無效的了。關閉後,兩個線程被關閉,後續對zookeeper句柄的任何訪問都將導致不確定的行爲,應該避免。

8.2 C綁定

C綁定有單線程和多線程庫。多線程庫易於使用,跟Java API非常相似。多線程庫將創建用於處理連接維持和回調的IO線程與事件分發線程。通過暴露在多線程庫中使用的事件循環,單線程庫允許在事件驅動應用中使用ZooKeeper

有兩個共享庫:zookeeper_stzookeeper_mt。前者提供了異步API和回調,可集成到應用程序的事件循環中。這個庫存在的目的僅僅是爲了支持沒有pthread可用,或者pthread不穩定的平臺(如FreeBSD 4.x)。在其他場合,應用開發者應該鏈接zookeeper_mt,它同時支持同步和異步API

8.2.1 安裝

如果從Apache代碼倉庫檢出的代碼創建客戶端庫,執行下面的步驟。如果從apache下載的工程源代碼包開始創建,則跳到步驟3

1.ZooKeeper頂級目錄(.../trunk)執行ant compile_jute。這將在../trunk/src/c目錄中創建"generated"目錄。

2.修改當前目錄爲../trunk/src/c,執行autoreconf -if,以啓動autoconfautomakelibtool。請確認安裝了2.59或者更高版本的autoconf。跳到步驟4

3.如果從工程源代碼包開始創建,解壓縮源代碼包,cdzookeeper-x.x.x/src/c目錄。

4.執行./configure <your-options>以生成makefile。對於這一步,configure工具支持下述有用的選項:

l         --enable-debug 啓用優化和調試信息。(默認是禁用的)

l         --without-syncapi 禁止同步API支持,不創建zookeeper_mt庫。(默認是啓用的)

l         --disable-static 不創建靜態庫。(默認是啓用的)

l         --disable-shared 不創建共享庫。(默認是啓用的)

l         注意:關於執行configure的一般信息,請看INSTALL文件。

5.執行make或者make install,創建並且安裝庫。

6.要生成ZooKeeper APIdoxygen文檔,可執行doxygen-doc。所有文檔將放置到docs子目錄中。默認情況下,這個命令只生成HTML。關於其他文檔格式的信息,請執行./congiure --help

8.2.2 使用C客戶端

要測試客戶端,可運行ZooKeeper服務器(關於如何運行,請看工程wiki頁面的指示),使用作爲安裝過程一部分創建的某個cli應用程序來連接到服務器。下面的例子顯示了使用cli_mt(多線程,與zookeeper_mt庫一同創建),但是也可以使用cli_st(單線程,與zookeeper_st庫一同創建):

 

這個客戶端應用程序提供了一個執行簡單ZooKeeper命令的Shell。成功啓動並且連接到服務器之後,程序顯示shell提示符。現在就可以輸入ZooKeeper命令了。比如說,創建一個節點:

 

驗證節點已經創建:

 

應該可以看到根節點的子節點列表。

在應用程序中使用ZooKeeper API時,應該記住:

1.包含ZooKeeper頭文件:#include <zookeeper/zookeeper.h>

2.如果創建多線程客戶端,請使用-DTHREADED編譯器標誌,以啓用庫的多線程版本,並且鏈接到zookeeper_mt庫。如果創建單線程客戶端,不要使用-DTHREADED,並且鏈接到zookeeper_st庫。

關於JavaC的使用示例,請看程序結構和簡單示例

9 創建塊:ZooKeeper操作指南

本節描述開發者可對ZooKeeper服務器執行的所有操作。這些信息比本手冊前面章節的內容要更底層,但是比ZooKeeper API參考的層次要高。

9.1 處理錯誤

JavaC綁定都可能報告錯誤。Java客戶端綁定通過拋出KeeperException來報告錯誤,對異常對象調用code()可取得特定的錯誤碼。C客戶端綁定返回ZOO_ERRORS枚舉定義的錯誤碼。在兩個語言綁定中,API回調都指示結果值。關於所有可能的錯誤碼及其含義的詳細信息,請看API文檔(Java綁定的javadocC綁定的doxygen)。

9.2 連接到ZooKeeper

9.3 讀取操作

9.4 寫入操作

9.5 處理觀察

9.6 其他ZooKeeper操作

10 程序結構和簡單示例

11 轉向:常見問題和解決

現在你瞭解ZooKeeper了,它高效、簡單,你的程序可以工作,但是等等……,出了點問題了。下面是ZooKeeper用戶遇到的一些陷阱:

1.使用觀察的時候,必須處理已連接的觀察事件。ZooKeeper客戶端同服務器斷開連接期間,不會收到修改通知,直到重新連接。如果觀察一個節點的出現,則斷開連接期間會錯過節點的創建和刪除事件。

2.必須測試ZooKeeper服務失敗。一旦多數服務器不活動,ZooKeeper服務會失敗。問題是:你的程序可以處理這種情況嗎?在真實世界中,客戶端到ZooKeeper的連接可能會斷開(ZooKeeper服務器失敗和網絡分區是連接丟失的常見原因)。ZooKeeper客戶端庫會處理連接恢復,並且讓你知道發生了什麼,但是你必須保證可以正確恢復狀態和任何已失敗的未決請求。在實驗室確認程序是正確的,而不是在產品中:用由多個服務器組成的ZooKeeper服務進行測試,並且進行一些重啓。

3.客戶端和服務器使用的服務器列表應該一致。如果客戶端的列表只是真正的服務器列表的一部分,程序可以工作,雖然不是最優的;但是如果客戶端包含不在集羣中的服務器,則不能工作。

4.注意在哪裏放置事務日誌。事務日誌是ZooKeeper中最關乎性能的部分。返回響應之前,ZooKeeper必須將事務同步到媒體中。專用事務日誌設備是取得良好性能的關鍵。如果只有一個存儲設備,把跟蹤文件放到NFS中,並且增加snapshotCount;這不能解決問題,但是有一定的改善。

5.正確設置Java的最大堆大小。避免交換是非常重要的。大多數情況下,不必要地放入磁盤肯定會降低性能到不可接受的程度。記住,在ZooKeeper中,一切都是順序的,如果一個請求觸及磁盤,其他排隊的請求也會觸及磁盤。

爲避免交換,試試將堆大小設置爲擁有的物理內存大小減去操作系統和緩存需要的大小。確定最優堆大小的最好方法是執行負載測試。如果因爲一些原因而不能進行測試,請採取保守估計,選擇一個小於將導致交換的值。比如說,在4GB的機器上,3GB是一個保守的開始值。

 

除了正式的文檔之外,開發者還有其他一些信息來源:

l         ZooKeeper白皮書

Yahoo!研究院發佈的關於ZooKeeper設計和性能的權威討論。

l         API參考

關於ZooKeeper API的完整參考。

l         Hadoup 2008峯會上的ZooKeeper演講

Yahoo!研究院的Benjamin Reed關於ZooKeeper的視頻介紹。

l         護欄和隊列教程

Flavio Junqueira編寫的、關於使用ZooKeeper實現簡單護欄和生產者-消費者隊列的優秀教程。

l         ZooKeeper:一個可靠的、可伸縮的分佈式協調系統

Todd Hoff編寫的一篇文章(07/15/2008)。

l         ZooKeeper解決方案

關於ZooKeeper各種同步解決方案實現的虛擬層面的討論:事件處理、隊列、鎖和兩階段提交。

l         tbd

任何人想到的其他好的資料……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章