1 簡介
本文是爲想要建立使用ZooKeeper協調服務優點的分佈式應用的開發者準備的。本文包含理論信息和實踐信息。
本指南的前四節對各類ZooKeeper概念進行較高層次的討論。這些概念對於理解ZooKeeper是如何工做的,以及如何使用ZooKeeper來進行工做都是必要的。這幾節沒有代碼,但卻要求讀者對分佈式計算相關的問題較爲熟悉。
本文的大多數信息以可獨立訪問的參考材料的形式存在。可是,在編寫第一個ZooKeeper應用程序以前,你應該至少讀過ZooKeeper數據模型和ZooKeeper基本操做。此外,簡單示例程序也有助於理解ZooKeeper客戶端應用程序的基本結構。
2 ZooKeeper數據模型
ZooKeeper有一個分層的名字空間,跟分佈式文件系統很類似。惟一的不一樣是,名字空間中的每一個節點均可以有關聯的數據和子節點。這就像一個容許文件也是目錄的文件系統。節點路徑老是表達爲規則的、斜槓分隔的絕對路徑,不存在相對路徑。路徑可使用任何Unicode字符,可是須要遵循下列限制:
一、不能使用空字符(\?)。(這在C綁定中會致使問題)
二、由於不能正確顯示,或者容易弄混淆,不能使用這些字符:\ - \和\ - \?。
三、不容許使用這些字符:\? - uF8FFF、\? - uFFFF、\uXFFFE - \uXFFFF(X是1到E之間的一個數字)、\? - \?。
四、可使用小數點,可是不能單獨使用.和..來指示路徑中的節點,由於ZooKeeper不使用相對路徑。/a/b/./c或者/a/b/../c是無效的。
五、記號zookeeper是保留的。
2.1 ZNode
ZooKeeper樹中的節點稱做znode。znode會維護一個包含數據修改和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以多種方式跟蹤時間:
一、zxid
每次修改ZooKeeper狀態都會收到一個zxid形式的時間戳,也就是ZooKeeper事務ID。事務ID是ZooKeeper中全部修改總的次序。每一個修改都有惟一的zxid,若是zxid1小於zxid2,那麼zxid1在zxid2以前發生。
二、版本號
對節點的每次修改將使得節點的版本號增長一。版本號有三種:version(znode數據修改的次數)、cversion(znode子節點修改的次數),以及aversion(znode的ACL修改次數)。
三、tick
多服務器ZooKeeper中,服務器使用tick來定義狀態上傳、會話超時、節點間鏈接超時等事件的時序。tick僅被最小會話超時(2倍的tick時間)間接使用:若是客戶端要求小於最小會話超時的時間,服務器將告知客戶端,實際使用的是最小會話超時。
四、真實時間
除了在建立和修改znode時將時間戳放入stat結構體中以外,ZooKeeper不使用真實時間,或者說時鐘時間。
2.3 ZooKeeper的Stat結構體
ZooKeeper中每一個znode的Stat結構體由下述字段構成:
czxid:建立節點的事務的zxid
mzxid:對znode最近修改的zxid
ctime:以距離時間原點(epoch)的毫秒數表示的znode建立時間
mtime:以距離時間原點(epoch)的毫秒數表示的znode最近修改時間
version:znode數據的修改次數
cversion:znode子節點修改次數
aversion:znode的ACL修改次數
ephemeralOwner:若是znode是臨時節點,則指示節點全部者的會話ID;若是不是臨時節點,則爲零。
dataLength:znode數據長度。
numChildren:znode子節點個數。
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"後綴,這讓客戶端命令都是相對於指定的根的(相似於Unix的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,全部路徑將是相對於這個根的:獲取/設置/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集羣,而不是客戶端來管理。客戶端與集羣創建會話時會提供上面討論的超時值。集羣使用這個值來肯定客戶端會話什麼時候過時。集羣在指定的超時時間內沒有獲得客戶端的消息時發生會話過時。會話過時時集羣將刪除會話的全部臨時節點,當即通知全部(觀察節點的)客戶端。此時已過時會話的客戶端仍是同集羣斷開鏈接的,不會被通知會話已通過期,直到(除非)客戶端從新創建到集羣的鏈接,這時候已過時會話的觀察纔會收到「會話已過時」通知。
已過時會話的觀察看到的狀態轉換過程示例:
一、已鏈接:會話創建,客戶端與集羣通訊中(客戶端/服務器通訊正常進行)
二、客戶端從集羣中分離
三、鏈接已斷開:客戶端失去同集羣的鏈接
四、時間流逝,超時時間事後,集羣讓會話過時,客戶端並不知道,由於它仍是同集羣斷開鏈接的。
五、時間流逝,客戶端與集羣間的網絡恢復正常。
六、已過時:最終客戶端從新鏈接到集羣,此時被通知會話已通過期。
創建會話時的另外一個參數是默認觀察。客戶端發生狀態改變時觀察會被通知。好比說,客戶端將在失去同服務器的鏈接,或者會話過時時被通知。觀察應該認爲初始狀 態是鏈接已經斷開(在客戶端庫向觀察發送任何狀態改變事件以前)。新建鏈接時發送給觀察的第一個事件一般是會話鏈接創建事件。
會話由客戶端發送的請求保持爲活動狀態。若是要空閒一段將致使超時的時間,客戶端將發送PING請求,保持會話是活動的。PING請求不只讓服務器知道客戶端仍然是存活的,也讓客戶端能夠確認,到服務器的鏈接依然是活動的。PING的時序足夠保守,確保可以在合理的時間內檢測到死掉的鏈接,從新鏈接到新的服務器。
一旦到服務器的鏈接成功創建,則進行同步或者異步操做時,一般有兩種狀況致使客戶端庫產生鏈接丟失事件(C綁定中的錯誤碼,Java中的異常:關於綁定特定的細節,請看API文檔):
一、應用程序對已經不存活/有效的會話進行操做
二、在有到服務器的未決操做(例如,有一個進行中的異步調用)時,客戶端斷開同服務器的鏈接
3.2.0版增長:SessionMovedException。有一種稱做SessionMovedException的 內部異常。一般客戶端看不到這個異常。在某鏈接上收到一個會話請求,可是這個會話已經重建到另外一個服務器上的時候會發生這種異常。致使這種錯誤的緣由一般 是,客戶端向服務器發送請求,可是數據分組被延遲,以至客戶端超時而且鏈接到一個新的服務器。延遲的分組到達先前的服務器的時候,服務器檢測到會話已經移 走,會關閉客戶端鏈接。客戶端一般看不到這個錯誤,由於客戶端不會從較早的鏈接上讀取數據(一般關閉了較早的鏈接)。兩個客戶端試圖使用已保存的會話ID和密碼從新創建相同的鏈接時會看到這種錯誤。其中一個客戶端將從新創建鏈接,而另外一個客戶端會被斷開鏈接(致使無限次地試圖從新創建鏈接/會話)。
4 ZooKeeper觀察
ZooKeeper中的全部讀操做:getData()、getChildren()和exists(),都有一個設置觀察做爲邊效應的選項。ZooKeeper對觀察的定義是:觀察事件是在被觀察數據發生變化時,發送給創建觀察的客戶端的一次性觸發器。對於這個定義,有三點值得關注:
一、一次觸發
觀察事件將在數據修改後發送給客戶端。好比說,若是客戶端執行getData("/znode1",true),而後/znode1的數據發生變化,或者被刪除,則客戶端將收到/znode1的觀察事件。若是再次修改/znode1,則不會給客戶端發送觀察事件,除非客戶端再執行一次讀取操做,設置新的觀察。
二、發送給客戶端
這暗示着,在(致使觀察事件被觸發的)修改操做的成功返回碼到達客戶端以前,事件可能在去往客戶端的路上,可是可能不會到達客戶端。觀察事件是異步地發送給觀察者(客戶端)的。ZooKeeper會保證次序:在收到觀察事件以前,客戶端不會看到已經爲之設置觀察的節點的改動。網絡延遲或者其餘因素可能會讓不一樣的客戶端在不一樣的時間收到觀察事件和更新操做的返回碼。這裏的要點是:不一樣客戶端看到的事情都有一致的次序。
三、爲哪些數據設置觀察
節點有不一樣的改動方式。能夠認爲ZooKeeper維護兩個觀察列表:數據觀察和子節點觀察。getData()和exists()設置數據觀察。getChildren()設置子節點觀察。此外,還能夠認爲不一樣的返回數據有不一樣的觀察。getData()和exists()返回節點的數據,而getChildren()返回子節點列表。因此,setData()將爲znode觸發數據觀察。成功的create()將爲新建立的節點觸發數據觀察,爲其父節點觸發子節點觀察。成功的delete()將會爲被刪除的節點觸發數據觀察以及子節點觀察(由於節點不能再有子節點了),爲其父節點觸發子節點觀察。
觀察維護在客戶端鏈接到的ZooKeeper服 務器中。這讓觀察的設置、維護和分發是輕量級的。客戶端鏈接到新的服務器時,全部會話事件將被觸發。同服務器斷開鏈接期間不會收到觀察。客戶端從新鏈接 時,若是須要,先前已經註冊的觀察將被從新註冊和觸發。一般這都是透明的。有一種狀況下觀察事件將丟失:對尚未建立的節點設置存在觀察,而在斷開鏈接期 間建立節點,而後刪除。
4.1 ZooKeeper關於觀察的保證
一、觀察與其餘事件、其餘觀察和異步迴應是順序的。ZooKeeper客戶端庫保證一切都是按順序分發的。
二、客戶端將在看到znode的新數據以前收到其觀察事件。
三、觀察事件的次序與ZooKeeper服務看到的更新次序一致。
4.2 關於觀察須要記住的
一、觀察是一次觸發的:若是想在收到觀察事件以後收到將來修改的通知,必須再次設置觀察。
二、由於觀察是一次觸發的,而收到觀察事件和發送新的請求、再次創建觀察之間是有延遲的,因此不能可靠地觀察到節點的全部修改。應該要準備處理在收到觀察事件和再次設置觀察之間,節點被屢次修改的狀況。(能夠不處理,但至少要知道這種狀況是可能的)
三、一個觀察對象,或者函數/上下文對,只會由於某個通知而觸發一次。好比說,對同一個文件使用exists和getData調用,設置相同的觀察對象,而後文件被刪除,則觀察對象只會被調用一次,帶有文件刪除通知。
四、與服務器斷開鏈接期間(好比說,服務器故障)不能收到任何觀察事件,直到鏈接從新創建。所以,會話事件是發送給全部未決觀察處理器的。可以使用會話事件進入到安全模式:斷開鏈接期間不會收到任何事件,進程應該謹慎操做。
五、使用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,其中scheme是id對應的鑑權模式。好比說,ip:172.16.16.1是地址爲172.16.16.1的主機的ID。
客戶端鏈接到ZooKeeper,驗證自身的時候,ZooKeeper將全部對應客戶端的ID都關聯到客戶端鏈接上。客戶端試圖存取節點的時候,ZooKeeper會在節點的ACL中校驗這些ID。ACL由(scheme:expression,perms)對組成。expression的格式是特定於scheme的。好比說,(ip:19.22.0.0/16,READ)給予任何IP地址以19.22開頭的客戶端以READ權限。
5.1 ACL權限
ZooKeeper支持下述權限:
一、CREATE:可建立子節點
二、READ:可獲取節點數據和子節點列表
三、WRITE:可設置節點數據
四、DELETE:可刪除子節點
五、ADMIN:可設置節點權限
從WRITE權限中分離出CREATE和DELETE能夠取得更好的訪問控制。使用CREATE和DELETE的狀況:
一、但願A能夠設置節點數據,可是不能CREATE或者DELETE子節點。
二、沒有DELETE的CREATE權限:客戶端經過在某父目錄中建立節點來建立請求。此時但願全部客戶端能夠添加節點,可是隻有請求處理器能夠刪除節點。(這與文件的APPEND權限相似)
此外,ADMIN權限存在的緣由是,ZooKeeper沒有文件全部者的概念。某些狀況下ADMIN權限能夠指定實體的全部者。ZooKeeper不支持LOOKUP權限(目錄上的、容許進行LOOKUP的執行權限位,即便不能列出目錄內容)。每一個用戶都隱含地擁有LOOKUP權限。這僅僅讓用戶能夠取得節點狀態。(問題是,若是想對一個不存在的節點進行zoo_exists()調用,沒有權限能夠檢查)
5.1.1 內置的ACL模式
ZooKeeper內置下述ACL模式:
一、world具備單獨的ID,表明任何用戶。
二、auth不使用任何ID,表明任何已確認用戶。
三、digest使用username:password字符串來生成MD5散列值,用做ID。身份驗證經過發送明文的username:password字符串來進行。用在ACL表達式中時將是username:base64編碼的SHA1密碼摘要。
四、ip使用客戶端主機IP做爲ID。ACL表達式的形式是addr/bits,表示addr的最高bits位將與客戶端主機IP的最高bits位進行匹配。
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中。
有兩個內置的身份驗證插件:id和digest。可經過系統屬性添加額外的插件。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的一致性保證:
一、順序一致性:一個客戶端的更新將以發送的次序被應用。
二、原子性:更新要麼成功,要麼失敗,沒有部分結果。
三、單一系統鏡像:不管鏈接到哪一個服務器,客戶端將看到一樣的視圖。
四、可靠性:一旦應用了某更新,結果將是持久的,直到客戶端覆蓋了更新。這個保證有兩個推論:
(1)若是客戶端獲得成功的返回碼,則更新已經被應用。某些失敗狀況下(通訊錯誤、超時等),客戶端不知道更新是否已經應用。咱們採起措施保證最小化失敗,但這個保證只對成功的返回碼有效。(這稱做是Paxos中的單一條件)
(2)服務器從失敗恢復時,客戶端經過讀請求或者成功更新看到的任何更新,都不會回滾。
五、及時性:保證客戶端的系統視圖在某個時間範圍(大約爲十幾秒)內是最新的。在此範圍內,客戶端要麼可看到系統的修改,要麼檢測到服務終止。
使用這些一致性保證,就能夠很容易地單獨在ZooKeeper客戶端構建如領導者選舉、護欄、隊列以及可恢復的讀寫鎖等高層功能。更多細節請看Recipes and Solutions。
注意:有時候開發者會錯誤地假定一個ZooKeeper實際上沒有提供的保證:
六、跨客戶端視圖的併發一致性
ZooKeeper並不保證在某時刻,兩個不一樣的客戶端具備一致的數據視圖。由於網絡延遲的緣由,一個客戶端可能在另外一個客戶端獲得修改通知以前進行更新。假定有兩個客戶端A和B。若是客戶端A將一個節點/a的值從0修改成1,而後通知客戶端B讀取/a,客戶端B讀取到的值可能仍是0,這取決於它鏈接到了哪一個服務器。若是客戶端A和B讀取到相同的值很重要,那麼客戶端B應該在執行讀取以前調用sync()方法。
因此,ZooKeeper自己不保證修改在多個服務器間同步地發生,可是可使用ZooKeeper原語來構建高層功能,提供有用的客戶端同步。(更多信息,請看ZooKeeper Recipes)
8 綁定
ZooKeeper客戶端庫以兩種方式提供:Java和C。下面幾節描述這兩種綁定。
8.1 Java綁定
ZooKeeper的Java綁定由兩個包組成:org.apache.zookeeper和org.apache.zookeeper.data。組成ZooKeeper的其餘包由內部使用或者是服務器實現的組成部分。org.apache.zookeeper.data由簡單地用做容器的類構成。
ZooKeeper Java客戶端使用的主要類是ZooKeeper類。這個類的兩個構造函數的不一樣僅僅在於可選的會話ID和密碼。ZooKeeper支持進程的不一樣實例間的會話恢復。Java程序能夠將會話ID和密碼保存到穩態存儲中,而後重啓、恢復程序先前實例使用的會話。
建立ZooKeeper對象的時候,會同時建立兩個線程:一個IO線程和一個事件線程。全部IO在IO線程中發生(使用Java NIO)。全部事件回調則在事件線程中進行。重連到ZooKeeper服務器和維持心跳等會話維持活動在IO線程中進行。同步方法的迴應也在IO線程中進行。全部異步方法的迴應,以及觀察事件則在事件線程中處理。對於這個設計,有一些事情須要注意:
一、全部同步調用和觀察回調將按次序進行,一次一個。調用者能夠進行任何想要的處理,可是在此期間不會處理其餘回調。
二、回調不會阻塞IO線程或者同步調用的處理。
三、同步調用可能不會以正確的次序返回。好比說,假設客戶端進行下述處理:提交一個watch設置爲ture的、對節點/a的異步讀取,而後在讀取操做的完成回調中執行一個對/a的同步讀取。(多是很差的實現,可是是合法的,這只是一個簡單的例子)
若是在異步讀取和同步讀取之間,對/a進行了修改,則客戶端庫將在同步讀取返回以前接收到一個事件,代表/a已經被修改。可是由於完成回調阻塞了事件隊列,同步讀取將在觀察事件被處理以前返回/a的新值。
最後,關於關閉的規則很直接:一旦被關閉或者接收到致命事件(SESSION_EXPIRED和AUTH_FAILED),ZooKeeper對象就變成無效的了。關閉後,兩個線程被關閉,後續對zookeeper句柄的任何訪問都將致使不肯定的行爲,應該避免。
8.2 C綁定
C綁定有單線程和多線程庫。多線程庫易於使用,跟Java API很是類似。多線程庫將建立用於處理鏈接維持和回調的IO線程與事件分發線程。經過暴露在多線程庫中使用的事件循環,單線程庫容許在事件驅動應用中使用ZooKeeper。
有兩個共享庫:zookeeper_st和zookeeper_mt。前者提供了異步API和回調,可集成到應用程序的事件循環中。這個庫存在的目的僅僅是爲了支持沒有pthread可用,或者pthread不穩定的平臺(如FreeBSD 4.x)。在其餘場合,應用開發者應該連接zookeeper_mt,它同時支持同步和異步API。
8.2.1 安裝
若是從Apache代碼倉庫檢出的代碼建立客戶端庫,執行下面的步驟。若是從apache下載的工程源代碼包開始建立,則跳到步驟3。
一、在ZooKeeper頂級目錄(.../trunk)執行ant compile_jute。這將在../trunk/src/c目錄中建立"generated"目錄。
二、修改當前目錄爲../trunk/src/c,執行autoreconf -if,以啓動autoconf、automake和libtool。請確認安裝了2.59或者更高版本的autoconf。跳到步驟4。
三、若是從工程源代碼包開始建立,解壓縮源代碼包,cd到zookeeper-x.x.x/src/c目錄。
四、執行./configure <your-options>以生成makefile。對於這一步,configure工具支持下述有用的選項:
(1)--enable-debug 啓用優化和調試信息。(默認是禁用的)
(2)--without-syncapi 禁止同步API支持,不建立zookeeper_mt庫。(默認是啓用的)
(3)--disable-static 不建立靜態庫。(默認是啓用的)
(4)--disable-shared 不建立共享庫。(默認是啓用的)
(5)注意:關於執行configure的通常信息,請看INSTALL文件。
五、執行make或者make install,建立而且安裝庫。
六、要生成ZooKeeper API的doxygen文檔,可執行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時,應該記住:
一、包含ZooKeeper頭文件:#include <zookeeper/zookeeper.h>
二、若是建立多線程客戶端,請使用-DTHREADED編譯器標誌,以啓用庫的多線程版本,而且連接到zookeeper_mt庫。若是建立單線程客戶端,不要使用-DTHREADED,而且連接到zookeeper_st庫。
關於Java和C的使用示例,請看程序結構和簡單示例。
9 建立塊:ZooKeeper操做指南
本節描述開發者可對ZooKeeper服務器執行的全部操做。這些信息比本手冊前面章節的內容要更底層,可是比ZooKeeper API參考的層次要高。
9.1 處理錯誤
Java和C綁定均可能報告錯誤。Java客戶端綁定經過拋出KeeperException來報告錯誤,對異常對象調用code()可取得特定的錯誤碼。C客戶端綁定返回ZOO_ERRORS枚舉定義的錯誤碼。在兩個語言綁定中,API回調都指示結果值。關於全部可能的錯誤碼及其含義的詳細信息,請看API文檔(Java綁定的javadoc,C綁定的doxygen)。
9.2 鏈接到ZooKeeper
9.3 讀取操做
9.4 寫入操做
9.5 處理觀察
9.6 其餘ZooKeeper操做
10 程序結構和簡單示例
11 轉向:常見問題和解決
如今你瞭解ZooKeeper了,它高效、簡單,你的程序能夠工做,可是等等……,出了點問題了。下面是ZooKeeper用戶遇到的一些陷阱:
一、使用觀察的時候,必須處理已鏈接的觀察事件。ZooKeeper客戶端同服務器斷開鏈接期間,不會收到修改通知,直到從新鏈接。若是觀察一個節點的出現,則斷開鏈接期間會錯過節點的建立和刪除事件。
二、必須測試ZooKeeper服務失敗。一旦多數服務器不活動,ZooKeeper服務會失敗。問題是:你的程序能夠處理這種狀況嗎?在真實世界中,客戶端到ZooKeeper的鏈接可能會斷開(ZooKeeper服務器失敗和網絡分區是鏈接丟失的常見緣由)。ZooKeeper客戶端庫會處理鏈接恢復,而且讓你知道發生了什麼,可是你必須保證能夠正確恢復狀態和任何已失敗的未決請求。在實驗室確認程序是正確的,而不是在產品中:用由多個服務器組成的ZooKeeper服務進行測試,而且進行一些重啓。
三、客戶端和服務器使用的服務器列表應該一致。若是客戶端的列表只是真正的服務器列表的一部分,程序能夠工做,雖然不是最優的;可是若是客戶端包含不在集羣中的服務器,則不能工做。
四、注意在哪裏放置事務日誌。事務日誌是ZooKeeper中最關乎性能的部分。返回響應以前,ZooKeeper必須將事務同步到媒體中。專用事務日誌設備是取得良好性能的關鍵。若是隻有一個存儲設備,把跟蹤文件放到NFS中,而且增長snapshotCount;這不能解決問題,可是有必定的改善。
五、正確設置Java的最大堆大小。避免交換是很是重要的。大多數狀況下,沒必要要地放入磁盤確定會下降性能到不可接受的程度。記住,在ZooKeeper中,一切都是順序的,若是一個請求觸及磁盤,其餘排隊的請求也會觸及磁盤。爲避免交換,試試將堆大小設置爲擁有的物理內存大小減去操做系統和緩存須要的大小。肯定最優堆大小的最好方法是執行負載測試。若是由於一些緣由而不能進行測試,請採起保守估計,選擇一個小於將致使交換的值。好比說,在4GB的機器上,3GB是一個保守的開始值。java
本文參考自:node
http://www.uml.org.cn/sjjm/201309125.asp程序員