Apache > ZooKeeper
 

ZooKeeper 程式設計人員指南

開發使用 ZooKeeper 的分散式應用程式

簡介

這份文件是為希望建立分散式應用程式以利用 ZooKeeper 協調服務的開發人員提供的指南。它包含概念性和實務資訊。

本指南的前四個部分提供各種 ZooKeeper 概念的高階討論。這些討論對於了解 ZooKeeper 的運作方式以及如何使用它都是必要的。它不包含原始碼,但它假設您熟悉分散式運算相關的問題。第一組中的部分包括

接下來的四個區段提供實用的程式設計資訊。這些是

本書以附錄作結,其中包含連結至其他有用的、與 ZooKeeper 相關的資訊。

本文件中的大部分資訊都寫成可作為獨立參考文件存取。不過,在開始你的第一個 ZooKeeper 應用程式之前,你至少應該閱讀有關ZooKeeper 資料模型ZooKeeper 基本操作的章節。

ZooKeeper 資料模型

ZooKeeper 有一個階層式名稱空間,很像分散式檔案系統。唯一的差別是名稱空間中的每個節點都可以有與之相關聯的資料,以及子節點。這就像有一個檔案系統,允許檔案同時也是目錄。節點的路徑總是表示為正規、絕對、以斜線分隔的路徑;沒有相對參考。任何 Unicode 字元都可以在路徑中使用,但須符合下列限制

ZNodes

ZooKeeper 樹中的每個節點都稱為znode。Znode 維護一個狀態結構,其中包含資料變更的版本號碼、acl 變更。狀態結構也有時間戳記。版本號碼與時間戳記一起,讓 ZooKeeper 可以驗證快取並協調更新。每次 znode 的資料變更時,版本號碼就會增加。例如,每當客戶端擷取資料時,也會收到資料的版本。當客戶端執行更新或刪除時,它必須提供要變更的 znode 的資料版本。如果它提供的版本與資料的實際版本不符,更新就會失敗。(這個行為可以覆寫。

注意

在分散式應用程式工程中,節點這個字可以指一般的主機、伺服器、整體成員、客戶端程序等等。在 ZooKeeper 文件中,znode 指資料節點。伺服器指組成 ZooKeeper 服務的機器;法定人數對等節點指組成整體的伺服器;客戶端指任何使用 ZooKeeper 服務的主機或程序。

Znode 是程式設計師存取的主要實體。它們有幾個值得在此提到的特性。

監控

客戶端可以在 Znode 上設定監控。對該 Znode 的變更會觸發監控,然後清除監控。當監控觸發時,ZooKeeper 會傳送通知給客戶端。有關監控的更多資訊,請參閱 ZooKeeper 監控 一節。

資料存取

儲存在名稱空間中每個 Znode 的資料會以原子方式讀取和寫入。讀取會取得與 Znode 相關的所有資料位元組,而寫入會取代所有資料。每個節點都有存取控制清單 (ACL),用於限制誰可以做什麼。

ZooKeeper 並非設計為一般資料庫或大型物件儲存。相反地,它管理協調資料。此資料可以是組態、狀態資訊、集合點等形式。各種協調資料的共同特性是它們相對較小:以千位元組為單位。ZooKeeper 客戶端和伺服器實作有健全檢查,以確保 Znode 的資料小於 1M,但資料平均應該遠小於此。處理相對較大的資料大小會導致某些作業花費比其他作業更多時間,而且會影響某些作業的延遲,因為需要額外時間才能將更多資料移到網路和儲存媒體上。如果需要大型資料儲存,處理此類資料的常見模式是將其儲存在大量儲存系統上,例如 NFS 或 HDFS,並將指標儲存在 ZooKeeper 中的儲存位置。

臨時節點

ZooKeeper 也有短暫節點的概念。這些 Znode 存在於建立 Znode 的工作階段處於活動狀態時。當工作階段結束時,Znode 會被刪除。由於此行為,短暫 Znode 不允許有子節點。可以使用 getEphemerals() API 擷取工作階段的短暫節點清單。

getEphemerals()

擷取由該路徑的階段所建立的短暫節點清單。如果路徑為空,則會列出階段的所有短暫節點。使用案例 - 範例使用案例可能是,如果需要收集階段的短暫節點清單以進行重複資料輸入檢查,且節點是以順序方式建立,因此您不知道重複檢查的名稱。在這種情況下,getEphemerals() api 可用於取得階段的節點清單。這可能是服務發現的典型使用案例。

順序節點 -- 唯一命名

在建立 znode 時,您也可以要求 ZooKeeper 在路徑尾端附加單調遞增的計數器。此計數器是父 znode 的唯一計數器。計數器的格式為 %010d -- 也就是 10 個數字,並以 0 (零) 補齊 (計數器以這種方式格式化以簡化排序),例如「0000000001」。請參閱 佇列配方 以取得此功能的範例使用方式。注意:用於儲存下一個順序號碼的計數器是由父節點維護的有號 int (4 位元組),當遞增超過 2147483647 時,計數器會溢位 (導致名稱為「-2147483648」)。

容器節點

已於 3.5.3 中新增

ZooKeeper 有容器 znode 的概念。容器 znode 是特殊用途 znode,可用於配方,例如 leader、鎖定等。當容器的最後一個子項被刪除時,容器會成為伺服器在未來某個時間點刪除的候選對象。

考量此特性,您應準備好在容器 znode 內建立子項時取得 KeeperException.NoNodeException。也就是說,在容器 znode 內建立子項 znode 時,請務必檢查 KeeperException.NoNodeException,並在發生時重新建立容器 znode。

TTL 節點

已於 3.5.3 中新增

在建立 PERSISTENT 或 PERSISTENT_SEQUENTIAL znode 時,您可以選擇為 znode 設定 TTL (毫秒)。如果 znode 未在 TTL 內修改,且沒有子項,則它會成為伺服器在未來某個時間點刪除的候選對象。

注意:TTL 節點必須透過系統屬性啟用,因為它們在預設情況下會停用。請參閱 管理員指南 以取得詳細資訊。如果您嘗試在未設定適當系統屬性的情況下建立 TTL 節點,伺服器會擲回 KeeperException.UnimplementedException。

ZooKeeper 中的時間

ZooKeeper 以多種方式追蹤時間

ZooKeeper 統計結構

ZooKeeper 中每個 znode 的 Stat 結構由下列欄位組成

ZooKeeper 會話

ZooKeeper 客戶端透過使用語言繫結建立對服務的控制代碼來與 ZooKeeper 服務建立會話。建立後,控制代碼會從 CONNECTING 狀態開始,然後客戶端程式庫會嘗試連線到組成 ZooKeeper 服務的其中一個伺服器,然後切換到 CONNECTED 狀態。在正常操作期間,客戶端控制代碼會處於這兩個狀態之一。如果發生無法復原的錯誤,例如會話過期或驗證失敗,或者如果應用程式明確關閉控制代碼,則控制代碼會移至 CLOSED 狀態。下圖顯示 ZooKeeper 客戶端的可能狀態轉換

State transitions

若要建立客戶端會話,應用程式程式碼必須提供連線字串,其中包含以逗號分隔的 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」字尾。這會在將所有路徑解譯為相對於此根目錄時執行客戶端命令(類似於 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 會建立一個 ZooKeeper 會話,表示為一個 64 位數字,並將其指定給客戶端。如果客戶端連線到不同的 ZooKeeper 伺服器,它會在連線交握中傳送會話 ID。作為安全措施,伺服器會為會話 ID 建立一個密碼,任何 ZooKeeper 伺服器都可以驗證。當客戶端建立會話時,會話 ID 會連同密碼一起傳送給客戶端。當客戶端使用新的伺服器重新建立會話時,它會傳送此密碼和會話 ID。

呼叫 ZooKeeper 客户端程式庫以建立 ZooKeeper 會話的參數之一是會話逾時(以毫秒為單位)。客戶端會傳送請求的逾時,伺服器會回應它可以給客戶端的逾時。目前的實作要求逾時至少為 tickTime 的 2 倍(如伺服器設定中所設定),且最多為 tickTime 的 20 倍。ZooKeeper 客户端 API 允許存取協商的逾時。

當客戶端(會話)與 ZK 服務叢集分隔時,它會開始搜尋會話建立期間指定的伺服器清單。最後,當客戶端與其中至少一個伺服器之間的連線重新建立時,會話將再次轉換為「已連線」狀態(如果在會話逾時值內重新連線)或轉換為「已過期」狀態(如果在會話逾時後重新連線)。不建議為中斷建立新的會話物件(新的 ZooKeeper.class 或 c 繫結中的 zookeeper 控制權)。ZK 客户端程式庫會為您處理重新連線。特別是,我們在客户端程式庫中建置了啟發法,以處理「羊群效應」等事項... 只有在收到會話過期通知(強制)時才建立新的會話。

會話過期是由 ZooKeeper 集群本身管理,而非由用戶端管理。當 ZK 用戶端與集群建立會話時,它會提供上面詳述的「逾時」值。此值由集群用於判斷用戶端的會話何時過期。當集群在指定的會話逾時期間未收到用戶端的訊息(即無心跳)時,就會發生過期。在會話過期時,集群會刪除該會話擁有的任何/所有暫時節點,並立即通知任何/所有已連線的用戶端變更(正在監視那些 znode 的任何人)。此時,已過期會話的用戶端仍與集群斷開連線,在它能夠重新建立與集群的連線之前/除非它能夠重新建立連線,否則它不會收到會話過期通知。用戶端將保持斷線狀態,直到與集群重新建立 TCP 連線,此時已過期會話的監視器將收到「會話已過期」通知。

已過期會話的監視器所見已過期會話的範例狀態轉換

  1. 「已連線」:會話已建立,用戶端正在與集群通訊(用戶端/伺服器通訊運作正常)
  2. .... 用戶端與集群分區
  3. 「已斷線」:用戶端已失去與集群的連線
  4. .... 時間流逝,在「逾時」期間之後,集群會使會話過期,由於用戶端已與集群斷線,因此它不會看到任何訊息
  5. .... 時間流逝,用戶端重新獲得與集群的網路層級連線
  6. 「已過期」:最後,用戶端重新連線到集群,然後收到過期通知

ZooKeeper 會話建立呼叫的另一個參數是預設監視器。當用戶端發生任何狀態變更時,會通知監視器。例如,如果用戶端失去與伺服器的連線,則會通知用戶端,或者如果用戶端的會話過期,等等。此監視器應將初始狀態視為已斷線(即在用戶端程式庫將任何狀態變更事件傳送給監視器之前)。對於新的連線,傳送給監視器的第一個事件通常是會話連線事件。

會話由用戶端傳送的請求保持連線。如果會話閒置一段時間,會使會話逾時,則用戶端會傳送 PING 請求以保持會話連線。此 PING 請求不僅允許 ZooKeeper 伺服器知道用戶端仍處於活動狀態,而且還允許用戶端驗證它與 ZooKeeper 伺服器的連線是否仍處於活動狀態。PING 的時機保守到足以確保有合理的時間來偵測中斷的連線並重新連線到新的伺服器。

一旦與伺服器建立連線成功(已連線),基本上有兩種情況會讓 client lib 產生 connectionloss(c binding 中的結果代碼,Java 中的例外狀況 -- 請參閱 API 文件以取得特定 binding 的詳細資料),當執行同步或非同步作業時,以下任一情況成立

  1. 應用程式呼叫不再存活/有效的階段作業
  2. 當有待處理作業至該伺服器時,ZooKeeper client 與伺服器斷線,亦即,有待處理的非同步呼叫。

在 3.2.0 中新增 -- SessionMovedException。有一個內部例外狀況,一般來說 client 看不到,稱為 SessionMovedException。此例外狀況發生是因為在已在不同伺服器上重新建立的階段收到連線要求。此錯誤的正常原因是 client 傳送要求至伺服器,但網路封包延遲,因此 client 超時並連線至新伺服器。當延遲的封包到達第一個伺服器時,舊伺服器會偵測到階段已移轉,並關閉 client 連線。client 通常看不到此錯誤,因為他們不會從那些舊連線中讀取。(舊連線通常已關閉。)可能會看到此狀況的情況之一是,當兩個 client 嘗試使用已儲存的階段 ID 和密碼重新建立相同的連線時。其中一個 client 會重新建立連線,而第二個 client 會斷線(導致這對 client 無限期地嘗試重新建立其連線/階段)。

更新伺服器清單。我們允許 client 透過提供新的逗號分隔主機:埠對清單來更新連線字串,每個對應一個 ZooKeeper 伺服器。此函式呼叫機率負載平衡演算法,可能會導致 client 與其目前主機斷線,目標是達成新清單中每個伺服器預期的均等連線數。如果 client 連線的目前主機不在新清單中,此呼叫將永遠導致連線中斷。否則,決定會根據伺服器數量是否增加或減少,以及增加或減少多少而定。

例如,如果先前的連線字串包含 3 個主機,現在清單包含這 3 個主機和另外 2 個主機,連線至 3 個主機中每一個主機的 40% client 會移至新主機之一以平衡負載。此演算法會導致 client 以 0.4 的機率中斷與其連線的目前主機的連線,並在此情況下讓 client 連線至 2 個新主機之一,隨機選擇。

另一個範例 -- 假設我們有 5 個主機,現在更新清單移除其中 2 個主機,連線至 3 個剩餘主機的 client 將保持連線,而連線至 2 個已移除主機的所有 client 將需要移至 3 個主機之一,隨機選擇。如果連線中斷,client 會移至特殊模式,在此模式中,他會選擇一個新的伺服器來連線,使用機率演算法,而不仅仅是循環。

在第一個範例中,每個客戶端決定以 0.4 的機率斷開連線,但一旦做出決定,它會嘗試連線到隨機的新伺服器,而且僅在它無法連線到任何新伺服器時,它才會嘗試連線到舊伺服器。在找到伺服器或嘗試新清單中的所有伺服器並連線失敗後,客戶端會移回正常操作模式,在其中它會從 connectString 中挑選一個任意伺服器並嘗試連線到它。如果失敗,它會繼續嘗試輪詢不同的隨機伺服器。(請參閱上方用於最初選擇伺服器的演算法)

本機工作階段。新增於 3.5.0,主要由 ZOOKEEPER-1147 實作。

localSessionsUpgradingEnabled 停用時

localSessionsUpgradingEnabled 啟用時

ZooKeeper 監控

ZooKeeper 中的所有讀取操作 - getData()getChildren()exists() - 都可以選擇設定監控作為副作用。以下是 ZooKeeper 對監控的定義:監控事件是一次性觸發器,會傳送給設定監控的用戶端,並在設定監控的資料變更時發生。在這個監控定義中,有三個重點需要考量

監視器會在客戶端所連線的 ZooKeeper 伺服器上進行本地維護。這讓監視器可以輕量化地設定、維護和調度。當客戶端連線到新的伺服器時,監視器會針對任何階段事件觸發。在與伺服器斷線期間,將不會收到監視器。當客戶端重新連線時,任何先前註冊的監視器都會重新註冊,並在需要時觸發。一般來說,這一切都會透明地發生。有一種情況可能會錯過監視器:如果在斷線期間建立並刪除 znode,則會錯過對 znode 存在的監視器。

3.6.0 中的新功能:客戶端也可以在 znode 上設定永久的遞迴監視器,這些監視器在觸發時不會被移除,而且會針對註冊 znode 上的變更以及任何子 znode 遞迴觸發。

監控的語意

我們可以使用讀取 ZooKeeper 狀態的三個呼叫來設定監視器:exists、getData 和 getChildren。下列清單詳細說明了監視器可以觸發的事件以及啟用這些事件的呼叫

持續性、遞迴式監控

3.6.0 中的新功能:現在有一個上述標準監視器的變化,您可以設定一個在觸發時不會被移除的監視器。此外,這些監視器會觸發事件類型 NodeCreatedNodeDeletedNodeDataChanged,而且可以選擇對從監視器註冊的 znode 開始的所有 znode 遞迴觸發。請注意,不會為持續遞迴監視器觸發 NodeChildrenChanged 事件,因為這會是多餘的。

使用 addWatch() 方法設定持續監視器。觸發語意和保證(除了單次觸發之外)與標準監視器相同。關於事件的唯一例外是,遞迴持續監視器永遠不會觸發子變更事件,因為它們是多餘的。使用 removeWatches() 和監視器類型 WatcherType.Any 移除持續監視器。

移除監控

我們可以使用呼叫 removeWatches 移除註冊在 znode 上的監視器。此外,ZooKeeper 客戶端即使沒有伺服器連線,也可以透過將 local 標記設定為 true 來移除本地的監視器。下列清單詳細說明了在成功移除監視器後會觸發的事件。

ZooKeeper 對監控的保證

關於監控程式,ZooKeeper 維護這些保證

關於監控需要記住的事項

使用 ACL 的 ZooKeeper 存取控制

ZooKeeper 使用 ACL 來控制對其 znode(ZooKeeper 資料樹的資料節點)的存取。ACL 實作與 UNIX 檔案存取權限非常類似:它使用權限位元來允許/不允許對節點執行各種作業,以及位元套用的範圍。與標準 UNIX 權限不同,ZooKeeper 節點不受使用者(檔案擁有者)、群組和世界(其他)這三個標準範圍的限制。ZooKeeper 沒有 znode 擁有者的概念。相反地,ACL 會指定與這些 ID 相關聯的 ID 和權限組。

另請注意,ACL 僅適用於特定 znode。特別是,它不適用於子節點。例如,如果 /app 僅可由 ip:172.16.16.1 讀取,而 /app/status 可由全世界讀取,則任何人都可以讀取 /app/status;ACL 不是遞迴的。

ZooKeeper 支援可插入式驗證機制。Id 使用 scheme:expression 形式指定,其中 scheme 是 id 對應的驗證機制。有效表達式的集合由機制定義。例如,ip:172.16.16.1 是使用 ip 機制的地址為 172.16.16.1 的主機的 id,而 digest:bob:password 是使用 digest 機制的使用者名稱為 bob 的 id。

當客戶端連線到 ZooKeeper 並驗證自身時,ZooKeeper 會將對應於客戶端的 id 與客戶端連線關聯起來。當客戶端嘗試存取節點時,這些 id 會與 znode 的 ACL 進行比對。ACL 由 (scheme:expression, perms) 對組成。expression 的格式特定於機制。例如,對 (ip:19.22.0.0/16, READ) 的配對會給予 IP 位址以 19.22 開頭的任何客戶端 READ 權限。

ACL 權限

ZooKeeper 支援下列權限

CREATEDELETE 權限已從 WRITE 權限中拆分出來,以實現更精細的存取控制。CREATEDELETE 的案例如下

您希望 A 能夠對 ZooKeeper 節點執行設定,但無法 CREATEDELETE 子節點。

CREATE 不帶 DELETE:客戶端透過在父目錄中建立 ZooKeeper 節點來建立要求。您希望所有客戶端都能夠新增,但只有要求處理器可以刪除。(這有點像是檔案的 APPEND 權限。)

此外,由於 ZooKeeper 沒有檔案擁有者的概念,因此 ADMIN 權限存在。在某種意義上,ADMIN 權限將實體指定為擁有者。ZooKeeper 不支援 LOOKUP 權限(目錄上的執行權限位元,允許您 LOOKUP,即使您無法列出目錄)。每個人都隱含具有 LOOKUP 權限。這允許您對節點執行 stat,但僅限於此。(問題是,如果您想對不存在的節點呼叫 zoo_exists(),則沒有權限可以檢查。)

ADMIN 權限在 ACL 方面也扮演特殊角色:為了擷取 znode 的 ACL,使用者必須具有 READADMIN 權限,但如果沒有 ADMIN 權限,摘要雜湊值將會被遮罩掉。

內建 ACL 架構

ZooKeeeper 具有以下內建的方案

ZooKeeper C 應用程式介面

ZooKeeper C 函式庫提供下列常數

以下是標準 ACL ID

ZOO_AUTH_IDS 空的身分字串應解釋為「建立者的身分」。

ZooKeeper 伺服器附帶三個標準 ACL

ZOO_OPEN_ACL_UNSAFE 是完全開放的免費 ACL:任何應用程式都可以在節點上執行任何操作,並且可以建立、列出和刪除其子節點。ZOO_READ_ACL_UNSAFE 是任何應用程式都可讀取的存取權。CREATE_ALL_ACL 授予節點建立者所有權限。建立者必須先通過伺服器驗證(例如,使用「digest」架構),才能使用此 ACL 建立節點。

下列 ZooKeeper 作業會處理 ACL

應用程式使用 zoo_add_auth 函式向伺服器驗證自身。如果應用程式想要使用不同的架構和/或身分驗證,則可以多次呼叫此函式。

zoo_create(...) 作業會建立新的節點。acl 參數是與節點相關聯的 ACL 清單。父節點必須設定 CREATE 權限位元。

此作業會傳回節點的 ACL 資訊。節點必須設定 READ 或 ADMIN 權限。如果沒有 ADMIN 權限,摘要雜湊值會被遮罩掉。

此函式會用新的 ACL 清單取代節點的 ACL 清單。節點必須設定 ADMIN 權限。

以下是範例程式碼,它使用上述 API 來使用「foo」架構驗證自身,並建立具有建立權限的暫時節點「/xyz」。

注意

這是一個非常簡單的範例,目的是特別說明如何與 ZooKeeper ACL 互動。請參閱 .../trunk/zookeeper-client/zookeeper-client-c/src/cli.c,以取得 C 客戶端實作範例

#include <string.h>
#include <errno.h>

#include "zookeeper.h"

static zhandle_t *zh;

/**
 * In this example this method gets the cert for your
 *   environment -- you must provide
 */
char *foo_get_cert_once(char* id) { return 0; }

/** Watcher function -- empty for this example, not something you should
 * do in real code */
void watcher(zhandle_t *zzh, int type, int state, const char *path,
         void *watcherCtx) {}

int main(int argc, char argv) {
  char buffer[512];
  char p[2048];
  char *cert=0;
  char appId[64];

  strcpy(appId, "example.foo_test");
  cert = foo_get_cert_once(appId);
  if(cert!=0) {
    fprintf(stderr,
        "Certificate for appid [%s] is [%s]\n",appId,cert);
    strncpy(p,cert, sizeof(p)-1);
    free(cert);
  } else {
    fprintf(stderr, "Certificate for appid [%s] not found\n",appId);
    strcpy(p, "dummy");
  }

  zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);

  zh = zookeeper_init("localhost:3181", watcher, 10000, 0, 0, 0);
  if (!zh) {
    return errno;
  }
  if(zoo_add_auth(zh,"foo",p,strlen(p),0,0)!=ZOK)
    return 2;

  struct ACL CREATE_ONLY_ACL[] = {{ZOO_PERM_CREATE, ZOO_AUTH_IDS}};
  struct ACL_vector CREATE_ONLY = {1, CREATE_ONLY_ACL};
  int rc = zoo_create(zh,"/xyz","value", 5, &CREATE_ONLY, ZOO_EPHEMERAL,
                  buffer, sizeof(buffer)-1);

  /** this operation will fail with a ZNOAUTH error */
  int buflen= sizeof(buffer);
  struct Stat stat;
  rc = zoo_get(zh, "/xyz", 0, buffer, &buflen, &stat);
  if (rc) {
    fprintf(stderr, "Error %d for %s\n", rc, __LINE__);
  }

  zookeeper_close(zh);
  return 0;
}

可插入的 ZooKeeper 驗證

ZooKeeper 在各種不同的環境中執行,並使用各種不同的驗證架構,因此它有一個完全可插入的驗證架構。甚至內建驗證架構也使用可插入的驗證架構。

要了解驗證架構如何運作,您必須先了解兩個主要的驗證操作。架構必須先驗證用戶端。這通常在用戶端連線到伺服器時執行,並包含驗證從用戶端傳送或收集的資訊,並將其與連線關聯。架構處理的第二個操作是尋找與用戶端對應的 ACL 中的項目。ACL 項目是 <idspec、權限> 配對。idspec 可能與與連線關聯的驗證資訊進行單純的字串比對,或可能是針對該資訊評估的表達式。由驗證外掛程式的實作進行比對。以下為驗證外掛程式必須實作的介面

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)) 將資訊與連線關聯。

驗證外掛程式參與設定和使用 ACL。當為 znode 設定 ACL 時,ZooKeeper 伺服器會將項目的 id 部分傳遞給 isValid(String id) 方法。由外掛程式驗證 id 是否有正確的形式。例如,ip:172.16.0.0/16 是有效的 id,但 ip:host.com 則不是。如果新的 ACL 包含「auth」項目,isAuthenticated 會用來查看是否應將與連線關聯的此 scheme 的驗證資訊新增至 ACL。某些 scheme 不應包含在 auth 中。例如,如果指定 auth,用戶端的 IP 位址不會被視為應新增至 ACL 的 id。

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,只會使用其中一個。此外,所有伺服器都必須定義相同的外掛程式,否則使用外掛程式提供的驗證方案的用戶端將無法連線到某些伺服器。

新增於 3.6.0:可使用另一種抽象方式進行外掛式驗證。它提供其他參數。

public abstract class ServerAuthenticationProvider implements AuthenticationProvider {
    public abstract KeeperException.Code handleAuthentication(ServerObjs serverObjs, byte authData[]);
    public abstract boolean matches(ServerObjs serverObjs, MatchValues matchValues);
}

您會延伸 ServerAuthenticationProvider,而不是實作 AuthenticationProvider。您的 handleAuthentication() 和 matches() 方法將會收到其他參數 (透過 ServerObjs 和 MatchValues)。

一致性保證

ZooKeeper 是一種高性能、可擴充的服務。讀取和寫入操作都設計為快速,但讀取速度比寫入速度快。這是因為在讀取的情況下,ZooKeeper 可以提供較舊的資料,而這又是由於 ZooKeeper 的一致性保證

使用這些一致性保證,很容易建立較高層級的功能,例如領導者選舉、屏障、佇列和可撤銷的讀/寫鎖定,僅在 ZooKeeper 客户端(無需對 ZooKeeper 進行新增)中即可。有關更多詳細資訊,請參閱 配方和解決方案

注意

有時開發人員會錯誤地假設 ZooKeeper 並未實際做到的另一個保證。這是:*同時一致的跨客户端檢視*:ZooKeeper 不保證在任何時間點,兩個不同的客户端對 ZooKeeper 資料的檢視會相同。由於網路延遲等因素,一個客户端可能會在另一個客户端收到變更通知之前執行更新。考慮兩個客户端 A 和 B 的場景。如果客户端 A 將 znode /a 的值從 0 設定為 1,然後告訴客户端 B 讀取 /a,則客户端 B 可能會讀取舊值 0,具體取決於它連線到哪個伺服器。如果客户端 A 和客户端 B 讀取相同的值很重要,則客户端 B 應在執行讀取之前從 ZooKeeper API 方法呼叫 sync() 方法。因此,ZooKeeper 本身並不能保證變更會在所有伺服器上同步發生,但可以使用 ZooKeeper 原語來建構提供有用的客户端同步的高層級功能。(有關更多資訊,請參閱 ZooKeeper 配方

繫結

ZooKeeper 客户端程式庫有兩種語言:Java 和 C。下列各節說明這些語言。

Java 繫結

有兩個套件組成 ZooKeeper Java 繫結:org.apache.zookeeperorg.apache.zookeeper.data。組成 ZooKeeper 的其他套件在內部使用或屬於伺服器實作的一部分。org.apache.zookeeper.data 套件由產生的類別組成,這些類別僅用作容器。

ZooKeeper Java 客户端使用的主要類別是 ZooKeeper 類別。它的兩個建構函式僅由選用的會話 ID 和密碼有所不同。ZooKeeper 支援跨處理序執行個體的會話復原。Java 程式可以將其會話 ID 和密碼儲存在穩定儲存空間中,重新啟動,並復原程式先前執行個體使用的會話。

當建立 ZooKeeper 物件時,也會建立兩個執行緒:IO 執行緒和事件執行緒。所有 IO 都發生在 IO 執行緒上(使用 Java NIO)。所有事件回呼都發生在事件執行緒上。會話維護(例如重新連線到 ZooKeeper 伺服器和維護心跳)在 IO 執行緒上完成。同步方法的回應也在 IO 執行緒中處理。所有非同步方法和監控事件的回應都在事件執行緒中處理。有幾件事需要注意,這是此設計的結果

最後,與關閉相關的規則很簡單:一旦 ZooKeeper 物件關閉或收到致命事件(SESSION_EXPIRED 和 AUTH_FAILED),ZooKeeper 物件就會失效。在關閉時,兩個執行緒會關閉,而對 zookeeper 處理的任何進一步存取都是未定義的行為,應避免。

用戶端設定參數

下列清單包含 Java 客戶端的組態屬性。您可以使用 Java 系統屬性設定這些屬性中的任何一個。對於伺服器屬性,請查看管理指南的伺服器組態區段。ZooKeeper Wiki 也有關於ZooKeeper SSL 支援ZooKeeper 的 SASL 驗證的有用頁面。

C 繫結

C 繫結具有單執行緒和多執行緒函式庫。多執行緒函式庫最容易使用,且與 Java API 最類似。此函式庫會建立一個 IO 執行緒和一個事件傳送執行緒,用於處理連線維護和回呼。單執行緒函式庫允許透過公開多執行緒函式庫中使用的事件迴圈,在事件驅動應用程式中使用 ZooKeeper。

此套件包含兩個共用函式庫:zookeeper_st 和 zookeeper_mt。前者僅提供非同步 API 和回呼,用於整合到應用程式的事件迴圈中。此函式庫存在的唯一原因是支援沒有 pthread 函式庫或其不穩定的平台(例如 FreeBSD 4.x)。在所有其他情況下,應用程式開發人員都應連結到 zookeeper_mt,因為它包含對同步和非同步 API 的支援。

安裝

如果您要從 Apache 儲存庫的簽出建立客戶端,請遵循以下步驟。如果您要從 Apache 下載的專案原始碼套件建立,請跳到步驟 3

  1. 在 zookeeper-jute 目錄 (.../trunk/zookeeper-jute) 中執行 mvn compile。這會在 .../trunk/zookeeper-client/zookeeper-client-c 下建立一個名為「generated」的目錄。
  2. 變更目錄至*.../trunk/zookeeper-client/zookeeper-client-c* 並執行 autoreconf -if 以啟動 autoconfautomakelibtool。請確定已安裝 autoconf 版本 2.59 或更高版本。跳到步驟4
  3. 如果您要從專案原始碼套件建立,請解壓縮/解壓縮原始碼 tarball,並 cd 至* zookeeper-x.x.x/zookeeper-client/zookeeper-client-c* 目錄。
  4. 執行 ./configure <your-options> 以產生 makefile。以下是 configure 實用程式支援的一些選項,在這個步驟中可能很有用
注意

請參閱 INSTALL 以取得關於執行 configure 的一般資訊。1. 執行 makemake install 以建置函式庫並安裝它們。1. 若要產生 ZooKeeper API 的 doxygen 文件,請執行 make doxygen-doc。所有文件都將放置在名為 docs 的新子資料夾中。預設情況下,此命令只會產生 HTML。如需其他文件格式的資訊,請執行 ./configure --help

建立您自己的 C 應用程式用戶端

為了能夠在應用程式中使用 ZooKeeper C API,您必須記得

  1. 包含 ZooKeeper 標頭:#include <zookeeper/zookeeper.h>
  2. 如果您正在建置多執行緒用戶端,請使用 -DTHREADED 編譯器旗標進行編譯,以啟用函式庫的多執行緒版本,然後連結至 zookeeper_mt 函式庫。如果您正在建置單執行緒用戶端,請勿使用 -DTHREADED 進行編譯,並務必連結至 zookeeper_st 函式庫。
注意

請參閱 .../trunk/zookeeper-client/zookeeper-client-c/src/cli.c 以取得 C 用戶端實作範例

建構區塊:ZooKeeper 操作指南

本節探討開發人員可以在 ZooKeeper 伺服器上執行的所有作業。它比本手冊中較早的概念章節的資訊層級低,但比 ZooKeeper API 參考的層級高。它涵蓋這些主題

處理錯誤

Java 和 C 用戶端繫結都可能會報告錯誤。Java 用戶端繫結透過擲出 KeeperException 來執行此動作,在例外狀況中呼叫 code() 將傳回特定錯誤碼。C 用戶端繫結會傳回 ZOO_ERRORS 列舉中定義的錯誤碼。API 回呼會指示兩種語言繫結的結果碼。請參閱 API 文件(Java 的 javadoc、C 的 doxygen)以取得關於可能錯誤及其含義的完整詳細資訊。

連線到 ZooKeeper

在我們開始之前,您必須設定一個正在執行的 Zookeeper 伺服器,以便我們可以開始開發用戶端。對於 C 用戶端繫結,我們將使用多執行緒函式庫 (zookeeper_mt) 和用 C 編寫的簡單範例。若要與 Zookeeper 伺服器建立連線,我們使用 C API - zookeeper_init,其簽章如下

int zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags);

我們將展示在成功連線後輸出「已連線到 Zookeeper」的 client,否則會輸出錯誤訊息。我們將下列程式碼稱為 zkClient.cc

#include <stdio.h>
#include <zookeeper/zookeeper.h>
#include <errno.h>
using namespace std;

// Keeping track of the connection state
static int connected = 0;
static int expired   = 0;

// *zkHandler handles the connection with Zookeeper
static zhandle_t *zkHandler;

// watcher function would process events
void watcher(zhandle_t *zkH, int type, int state, const char *path, void *watcherCtx)
{
    if (type == ZOO_SESSION_EVENT) {

        // state refers to states of zookeeper connection.
        // To keep it simple, we would demonstrate these 3: ZOO_EXPIRED_SESSION_STATE, ZOO_CONNECTED_STATE, ZOO_NOTCONNECTED_STATE
        // If you are using ACL, you should be aware of an authentication failure state - ZOO_AUTH_FAILED_STATE
        if (state == ZOO_CONNECTED_STATE) {
            connected = 1;
        } else if (state == ZOO_NOTCONNECTED_STATE ) {
            connected = 0;
        } else if (state == ZOO_EXPIRED_SESSION_STATE) {
            expired = 1;
            connected = 0;
            zookeeper_close(zkH);
        }
    }
}

int main(){
    zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);

    // zookeeper_init returns the handler upon a successful connection, null otherwise
    zkHandler = zookeeper_init("localhost:2181", watcher, 10000, 0, 0, 0);

    if (!zkHandler) {
        return errno;
    }else{
        printf("Connection established with Zookeeper. \n");
    }

    // Close Zookeeper connection
    zookeeper_close(zkHandler);

    return 0;
}

使用前面提到的多執行緒程式庫編譯程式碼。

> g++ -Iinclude/ zkClient.cpp -lzookeeper_mt -o Client

執行 client。

> ./Client

從輸出中,如果你看到「已連線到 Zookeeper」以及 Zookeeper 的 DEBUG 訊息,表示連線成功。

陷阱:常見問題和疑難排解

現在你已經認識 ZooKeeper 了。它快速、簡單,你的應用程式運作正常,但等等... 有些地方不對勁。以下是 ZooKeeper 使用者會遇到的陷阱

  1. 如果你正在使用監控,你必須尋找已連線的監控事件。當 ZooKeeper client 與伺服器斷線時,你不會收到變更通知,直到重新連線。如果你正在監控 znode 出現,如果你在斷線時建立並刪除 znode,你會錯過該事件。
  2. 你必須測試 ZooKeeper 伺服器故障。只要大多數伺服器都處於活動狀態,ZooKeeper 服務就能在故障中存活下來。要問的問題是:你的應用程式能處理嗎?在實際情況中,client 與 ZooKeeper 的連線可能會中斷。(ZooKeeper 伺服器故障和網路分割是連線中斷的常見原因。)ZooKeeper client 程式庫會負責復原你的連線並讓你瞭解發生了什麼事,但你必須確保復原你的狀態和任何失敗的未完成要求。找出你是否在測試實驗室而不是在生產環境中做對了 - 使用由多個伺服器組成的 ZooKeeper 服務進行測試,並讓它們重新開機。
  3. client 使用的 ZooKeeper 伺服器清單必須與每個 ZooKeeper 伺服器擁有的 ZooKeeper 伺服器清單相符。如果 client 清單是 ZooKeeper 伺服器實際清單的子集,事情可以運作,儘管不是最佳化,但如果 client 清單列出不在 ZooKeeper 群集中的 ZooKeeper 伺服器,則不行。
  4. 小心放置該交易記錄。ZooKeeper 中效能最關鍵的部分是交易記錄。在傳回回應之前,ZooKeeper 必須將交易同步到媒體。專用的交易記錄裝置是持續良好效能的關鍵。將記錄放在忙碌的裝置上會對效能產生負面影響。如果你只有一個儲存裝置,請將追蹤檔案放在 NFS 上並增加 snapshotCount;它不能消除問題,但可以減輕問題。
  5. 正確設定 Java 最大堆積大小。避免交換非常重要。不必要地寫入磁碟幾乎一定會讓效能下降到無法接受的程度。請記住,在 ZooKeeper 中,所有內容都是依序的,因此如果一個要求寫入磁碟,所有其他排隊的要求都會寫入磁碟。若要避免交換,請嘗試將堆積大小設定為您擁有的實體記憶體量,減去作業系統和快取所需的量。找出最佳堆積大小的最佳方式是執行負載測試。如果因為某些原因而無法執行,請保守估計,並選擇一個遠低於會導致機器交換的限制數字。例如,在 4G 機器上,3G 堆積是一個保守的估計,可以從此開始。

其他資訊連結

除了正式文件之外,還有其他幾個資訊來源可供 ZooKeeper 開發人員使用。