1. 程式人生 > >《Apache Zookeeper 官方文件》-4 ZooKeeper程式設計指南

《Apache Zookeeper 官方文件》-4 ZooKeeper程式設計指南

原文連結  譯者:zivyu

簡介

對於想要利用ZooKeeper的協調服務來建立一個分散式應用的開發人員來說,這篇文章提供了指導。包含了一些概念和實際性操作的資訊。

這篇文章的前四個章節介紹了各種ZooKeeper的概念,這對理解ZooKeeper是怎麼工作的是必須的。沒有包含原始碼,但是它假設你對分散式處理有關的問題比較熟悉。這四個章節是:

  • ZooKeeper資料模型
  • ZooKeeper 會話
  • ZooKeeper Watches
  • 一致性保證

隨後的四個章節提供了實際的程式設計資訊,他們是:

  • 構建塊:ZooKeeper操作指南
  • 繫結
  • 程式結構,簡單的例子
  • 陷阱:常見問題和故障排查

ZooKeeper資料模型

ZooKeeper有一個層級名稱空間,和一個分散式檔案系統非常相似。唯一的不同是每個節點可以有關聯的資料,子節點也是。就像有一個檔案系統並且允許檔案是一個目錄。一個規範的、絕對的、斜槓分隔的路徑來表示一個節點路徑,沒有相對路徑。任何符合下列約束的的unicode字元可以被使用:

  • null字串(\u0000)不能是一個路徑名稱。
  • 下列字元不能被使用,因為不能很好的被展示:\u0001 – \u001F 和 \u007F – \u009F。
  • 下列字元是不允許的:\ud800 – uF8FF, \uFFF0 – uFFFF。
  • “.”字元可以作為另一個名字被使用,但是“.”和“..”不能單獨使用來表示一個節點路徑,因為ZooKeeper不使用相對路徑,下列是無效的:”/a/b/./c”或者 “/a/b/../c”。
  • “zookeeper”標記被保留。

ZNodes

在ZooKeeper樹中的每個節點被稱為一個znode。Znodes包含了一個stat資料結構,這個資料結構包括了資料變更的版本號、acl變更。stat資料結構也有時間戳,版本號和時間戳一起來允許ZooKeeper校驗快取和協調更新。每當一個znode的資料改變,版本號就會增加。例如:當一個客戶端取得資料,它同樣也接受資料的版本。並且,當一個客戶端執行一個更新或刪除操作,它必須提供資料的版本號。如果客戶端提供的的版本號和實際的版本號不匹配,更新操作將會失敗。

注意
在分散式應用應用中,node一詞可以用來表示一臺主機、一個伺服器、集中中的一個、一個客戶端程序等等。早ZooKeeper這邊文件中,znodes  
表示一個數據節點,Servers表示組成ZooKeeper服務中的機器,quorum peers 表示組成集合的機器,客戶端表示使用一個ZooKeeper服務的主機或程序。

Znodes是一個程式設計師訪問的主要實體,有許多在這裡值得提到特性:

Watches

客戶端可以在znodes上設定監聽器,znode的改變觸發這個監聽器然後清空這個監聽器。當一個監聽器被觸發,ZooKeeper傳送給客戶端一個通知。更多資訊可以檢視ZooKeeper Watches章節。

資料訪問

每個znode的上儲存的資料讀寫都是原子的,讀操作取出所有的和這個znode有關的所有資料,寫操作替換所有的資料。每個節點有一個訪問許可權列表(ACL)來限制誰可以做這些事情。

ZooKeeper沒有被設計成一個一般的資料庫或一個大型物件儲存。它管理協調資料,資料可以是配置、狀態資訊、集合點等的形式。各種各樣的資料有一個共同的屬性就是他們都很小:以千位元組為標準。ZooKeeper客戶端和伺服器有一個健康檢查來確保znodes的資料少於1M,但是資料平均應該更小。操作較大的資料將導致一些操作花費更多的時間,並且會影響一些操作的延遲,因為在網路和儲存媒介中移動更多的資料將需要額外的時間。如果需要儲存大資料,通常的處理是把資料儲存在一個大容量儲存系統中,並把儲存位置的指標儲存到ZooKeeper上。

臨時節點

ZooKeeper也臨時節點的概念。這些znodes存活的時間和建立這個節點的會話有效期是一樣的。當會話結束,節點被刪除。因為這種臨時節點的特性,臨時節點不允許有子節點。

順序節點——唯一名稱

當建立一個節點的時候,也可以請求ZooKeeper在路徑後面增加一個自增的計數器。對父節點來說,這個計數器是 唯一的。計數器是%010d的格式——是一個十位數,比如:<path>0000000001。

檢視Queue Recipe使用這個特性的示例,注意:這個計數器用來儲存下一個序列號是一個4位元組的數,當增加到2147483647 之後,計數器會溢位。

ZooKeeper中的時間

ZooKeeper以多種方式跟蹤時間:

  • Zxid:ZooKeeper狀態的每次變化都接收一個zxid(ZooKeeper事務id)形式的標記。這個展示了所有的ZooKeeper的變更順序。每次變更會有一個唯一的zxid,如果zxid1小於zxid2說明zxid1在zxid2之前發生。
  • Version numbers:節點的每次變化都會引起這個節點版本號之一的一次增加。這三個版本號是:version(一個節點的資料變化次數),cversion(一個節點的子節點變化次數),aversion(一個節點的ACL 變化次數)。
  • Tricks:當使用多個ZooKeeper服務,伺服器使用ticks來確定事件的時間,比如說狀態上傳、會話超時、連線超時等。這個tick時間僅僅通過最小會話超時時間間接的暴露出來;如果一個客戶端請求會話的超時時間小於最小超時時間,伺服器將會告訴客戶端實際的會話超時時間是最小超時時間。
  • Real Time:ZooKeeper不使用實時、時鐘時間。除了把時間戳放在stat結構中。

ZooKeeper Stat 結構

每個節點的Stat結構由下列欄位組成:

  • czxid:該資料節點被建立時的事務id。
  • mzxid:該節點最後一次被更新時的事務id。
  • ctime:節點被建立時的時間。
  • mtime:節點最後一次被更新時的時間。
  • version:這個節點的資料變化的次數。
  • cversion:這個節點的子節點 變化次數。
  • aversion:這個節點的ACL變化次數。
  • ephemeralOwner:如果這個節點是臨時節點,表示建立者的會話id。如果不是臨時節點,這個值是0。
  • dataLength:這個節點的資料長度。
  • numChildren:這個節點的子節點個數。

ZooKeeper會話

通過使用一種語言繫結來建立服務端的控制代碼,一個ZooKeeper客戶端可以和ZooKeeper服務建立會話。一旦建立,控制代碼開始在CONNECTING 狀態,客戶端庫嘗試連線組成ZooKeeper服務中的其中一個伺服器並且切換到CONNECTED狀態。在正常的操作期間將會是這兩種狀態之一。如果一個不可恢復的錯誤發生了,比如說會話過期或授權失敗,或者如果應用顯示地關閉了控制代碼,控制代碼將會到CLOSED狀態。下面的圖展示了一個ZooKeeper客戶端可能的狀態轉變。

為了建立一個客戶端會話,應用程式程式碼必須提供一個連線字串列表以逗號分隔開,主機:埠號成對出現,每個都相當於一個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”字尾可以被加在連線字串後面,這會執行客戶端命令導致所有的路徑都和這個跟路徑相關。如果使用像下面的示例:”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服務的每個使用者可以有不同的根路徑。這讓再使用變得非常簡單,因為每個使用者都可以編寫程式碼讓他的應用好像在”/”根路徑下,但實際的位置能在部署時決定。

當一個客戶端從ZooKeeper服務得到一個控制代碼,ZooKeeper建立了一個會話,表現為一個64位的數字,並把 它分配給客戶端。如果客戶端連線到一個不同的服務端,在連線握手的時候它將傳送這個會話id。作為一個安全措施,服務端給會話id建立了一個密碼,讓服務端能夠校驗。當客戶端建立會話的時候,這個密碼隨著會話id一起傳送給客戶端。每當客戶端與一個新的服務端恢復會話的時候,密碼會隨著會話id一起傳送過去。

客戶端呼叫建立會話的時候有一個引數是會話超時時間(毫秒),客戶端傳送一個要求的超時時間,服務端回覆一個他能給客戶端的超時時間。當前實現要求超時時間至少是2倍的tickTime,最大是20倍的tickTime。ZooKeeper客戶端API允許使用一個協商的超時時間。

當一個客戶端從ZK服務叢集成為分割槽,它將開始尋找在會話建立時期指定的服務端列表。最終,當客戶端和至少一個服務端聯通重新建立的時候,會話要麼轉變成“connected”狀態(如果在會話超時時間內恢復連線),要麼轉變成“expired”狀態(如果在超時時間之外恢復連線)。在斷開時建立一個新的會話是不可取的。ZK客戶端庫將處理連線。尤其是客戶端內部有方法來處理像“羊群效應”之類的事情。僅僅在你被通知會話過期的時候去建立一個新的會話。

ZooKeeper叢集自己管理會話過期,而不是由客戶端管理。當ZK客戶端和一個叢集建立會話,它提供一個“超時時間”。這個值被叢集使用來決定客戶端的會話是否過期。當叢集不能在指定的會話超時時間內從客戶端收到資訊,過期發生。在會話過期期間,叢集將刪除由這個會話建立的所有的臨時節點,並且立即通知連線的客戶端這個改變。此時,會話過期的客戶端依然和叢集式斷開的,它不會收到通知直到它能和叢集重新建立連線。這個客戶端將保持斷開狀態直到和叢集的TCP連線重新建立,並且在這個時候,過期會話的監聽將會收到“會話過期”通知。

對於一個過期的會話,監聽器所看到的狀態轉變:

  1. “connected”:會話被建立,並且客戶端能和叢集交流
  2. ……客戶端從叢集被分割
  3. “disconnected”:客戶端與叢集丟失了聯絡
  4. ……時間流逝,在超時時間之後,叢集已經讓這個會話過期,而客戶端沒看到什麼,因為它已經從叢集斷開連線了
  5. ……時間流逝,客戶端恢復網路和叢集聯通
  6. “expired”:最後客戶端與叢集重新連線,然後收到過期的通知

ZooKeeper會話建立的另一個引數是預設監聽器。當客戶端的一些狀態改變發生,監聽器會收到通知。比如如果客戶端丟失與服務端的連線,客戶端將會收到通知,或客戶端的會話到期等。這個監聽器應該考慮初始狀態到斷開連線。對於一個新的連線,第一給發給監聽器的事件就是會話連線事件。

客戶端通過傳送請求保持會話存活。如果會話在一段時間內空閒將會導致會話超時,客戶端將會發送PING請求保持會話存活。這個PING請求不僅僅讓ZooKeeper服務端知道客戶端是存活的,而且讓客戶端檢查它的和ZooKeeper 服務端的連線也是存活的。PING的時間是足夠保守的合理時間,來發現死掉的連線和一個新的服務端重新連線。

一旦成功建立一個到服務端的連線,當客戶端發生connectionloss異常 時有兩種基本的情況,在執行一個同步或者非同步的操作時:

  1. 應用呼叫一個操作,但是會話不再存活。
  2. 當等待一個操作的時候ZooKeeper客戶端從服務端斷開連線,比如說:等待一個非同步呼叫。

3.2.0新增——SessionMovedException。有一個內部的異常,通常不會被客戶端發現,被稱為SessionMovedException。一個已經連線的會話但是重新連線到了一個不同的伺服器上接收了一個請求,這個異常就會發生。這個錯誤的正常原因是一個客戶端傳送了一個請求到一個服務端,但是網路資料包延遲了,所以客戶端超時並連線到了一個新的伺服器。當延遲的資料包到達了第一個伺服器,這個服務端發現這個會話已經被移除了並且關閉了這個客戶端連線。客戶端一般不會發現這個錯誤因為它們不在從老的連線讀取資料(老的連線一般被關閉了)。這種事情發生的另一種情況是當兩個客戶端使用一個儲存的會話id和密碼來嘗試恢復相同的連線時,只有一個客戶端能夠恢復連線,另一個客戶端將會斷開。

更新伺服器列表。我們允許一個客戶端更新連線字串通過提供一個新的逗號分隔的主機:埠號列表,每個都是一個伺服器。函式呼叫一個概率負載均衡演算法會引起客戶端斷開與當前主機的連線,來使在新列表中的每個伺服器達到與預期一致的數量。萬一客戶端連線的當前主機不在新的列表中,這個呼叫會引起連線被刪除。另外,這個決定基於是否伺服器的數量增加或減少了多少。

比如說,如果之前的連線包含三個主機,現在的連線多了兩個主機,連線到每個主機的客戶端的40%為了負載均衡將會移動到新的主機上去。這個演算法會引起客戶端斷掉它當前與伺服器的連線,這個概覽是0.4,並且客戶端隨機選擇兩個新主機中的一個連線。

另一個例子,假設我們有5個主機,然後現在更新列表移除兩個主機,連線到剩餘三臺主機的客戶端依然保持連線,然而所有連線到已被移除主機的客戶端都需要移到剩下三臺主機的一臺上,並且這種選擇是隨機的。如果連線斷開,客戶端進入一個特殊的模式並使用概率演算法選擇一個新的伺服器,而不僅僅只是迴圈。

在第一個例子中,每個客戶端決定斷開連線的概覽為0.4,但是一旦做了決定,它將會隨機的連線到一個新的伺服器,僅僅當它不能連線到任何一臺新的伺服器上時,它將嘗試連線舊的伺服器。當找到一個伺服器或者新列表中所有的伺服器都連線失敗的時候,客戶端回到操作正常模式,選擇一個任意的伺服器並嘗試連線它,如果連線失敗,它會繼續嘗試不同的隨機的伺服器,並一直迴圈下去。

ZooKeeper Watches

ZooKeeper中所有的讀操作——getData(), getChildren()和 exists()—可以選擇設定 一個監聽器。這是ZooKeeper’s一個監聽器的定義:一個監聽事件是一次性觸發,當一個被設定監聽的資料改變時,傳送給設定這個監聽器的客戶端。在這個監聽器的定義中,有三個要點:

  • 一次性觸發:當資料改變的時候一個監聽事件會被髮送給客戶端。比如說,如果一個客戶端做了getData(“/znode1”, true)操作,然後 /znode1下的資料被改變或者刪除了,客戶端將得到/znode1的一個監聽事件。如果/znode1節點再次發生改變,沒有監聽事件會被髮送除非客戶端做了別的設定了一個新的監聽器。
  • 傳送到客戶端:這意味著事件正在傳送給客戶端的途中,但是在操作成功的返回碼到達發起這個變更操作的客戶端之前,事件可能還沒到達監聽的客戶端。ZooKeeper提供了一個有序保證:在它第一次看到監聽事件之前,它永遠不會看到它設定的監聽改變。網路延遲或別的因素可能會引起不同的客戶端看見監聽器和更新操作的返回碼,在不同的時間。關鍵得一點是不同的客戶端看見的每件事有一個一致的順序。
  • 被設定監聽的資料:這是指一個節點能變化的不同方式。可以認為ZooKeeper有兩個監聽器列表:資料監聽和子節點監聽。getData()和exists()設定資料監聽器。 getChildren()設定子節點監聽器。二選一,根據返回資料的型別來設定監聽器。getData()和exists()返回節點的資料資訊,然而getChildren()返回一個子節點列表。因此,setData()會觸發資料監聽器。一個成功的 create()會觸發一個數據監聽器。一個delete()會觸發資料監聽器和子節點監聽器。

在ZooKeeper伺服器中,當客戶端連線的時候,監聽器被儲存在本地。這使得監聽器輕量級的被設定、儲存、分發。當一個客戶端連線一個新的伺服器,監聽器會觸發一些會話事件。當從伺服器斷開連線的時候,不會受到監聽器。當一個客戶端重新連線,如果需要的話,之前註冊的監聽器會被註冊和觸發。有一個監聽器可能丟失的情況:如果在斷開連線期間,一個節點被建立和刪除,一個已存在的節點的監聽器還沒有建立,將丟失。

監聽器的意思

我們能在三種呼叫讀取ZooKeeper狀態的情況下設定監聽器:exists,getData和getChildren,下面的列表是一個監聽器觸發的事件的詳細情況:

  • 建立事件:exists的呼叫
  • 刪除事件:exists,getData和getChildren的呼叫
  • 改變事件:exists,getData的呼叫
  • 子節點事件:getChildren的呼叫

移除監聽器

我們可以呼叫removeWatches來移除一個註冊在節點上的監聽器。同樣的,一個ZooKeeper客戶端在沒有伺服器連線的情況下能移除本地的監聽器,通過設定本地的標記為true。下面是事件的詳細列表監聽器成功的被移除後觸發:

  • 子節點移除事件:呼叫getChildren增加的監聽器。
  • 資料移除事件:呼叫exists或getData增加的監聽器。

ZooKeeper對監聽器的保證

對於監聽器,ZooKeeper有下列的保障:

  • 監聽器和另外的事件,另外的監聽器和非同步的回覆是有序的。ZooKeeper 客戶端庫確保每件事都有序分發。
  • 一個客戶端看到這個節點的新的資料之前,會先看到他監聽的節點的一個監聽事件。
  • 從ZooKeeper 來的監聽事件的順序對應於ZooKeeper 服務看到的更新的順序。

關於監聽器要記住的事情

  • 監聽器是一次觸發的,如果你得到了一個監聽事件並且想繼續得到未來的事件通知,你必須設定一個另外的監聽器。
  • 因為監聽器是一次觸發的,就會在得到事件和傳送請求設定新的監聽器之間有一個延遲,你不能看到ZooKeeper的節點上每次 改變。準備好處理在得到事件和設定監聽器之間節點多次改變的情況(你或許不太關心,但至少要意識這會發生)。
  • 一個監聽器物件或一個函式/上下文對,為一個事件只會被觸發一次。比如說,如果相同的監聽器在一次exists或getData呼叫中被註冊到了相同的檔案,並且檔案被刪除,對於該檔案刪除的通知,監聽器物件只會被呼叫一次。
  • 當你從伺服器斷開連線,在恢復連線之前,你不會得到任何監聽器。由於這個原因,會話事件會被髮送給所有的未處理的監聽器。使用會話事件進入一個安全模式:在斷開期間,你不會收到事件,所以你的程序在這種模式下應該小心行事。

ZooKeeper使用ACLs控制訪問

ZooKeeper使用ACLs來控制訪問它的節點(ZooKeeper資料樹上的資料節點)。ACL的實現和UNIX檔案訪問許可權非常相似:它使用許可權位來允許/拒絕對節點和位適用範圍的各種操作。不像標準的UNIX許可權,一個ZooKeeper節點沒有限制在這三個標準的範圍:user (檔案擁有者)、group、world 。ZooKeeper沒有節點擁有者的概念,取而代之的是,一個ACL指定ids和id相關的許可權的集合。

還請注意一個ACL只適用於一個指定的節點,它也不適用於子節點。比如說,如果 /app節點只能被ip:172.16.16.1讀取, /app/status是全部可讀的,任何人都 可以讀取/app/status。ACLs不是遞迴的。

ZooKeeper支援可插拔式的認證方案。Ids指定使用這個形式scheme:idscheme是id對應的授權方案,比如說,ip:172.16.16.1是一個主機地址為172.16.16.1的id

當一個客戶端連線ZooKeeper並進行認證,ZooKeeper把符合這個客戶端的所有ids聯絡起來。當客戶端嘗試存取一個節點的時候,這些ids用來檢查一個節點的ACLs。ACLs由成對(scheme:expression, perms)的組成。expression的格式指定了許可權,比如說,(ip:19.22.0.0/16, READ)給所有的以19.22開頭的IP地址的客戶端讀的許可權。

ZooKeeper支援下列許可權:

  • CREATE:可以建立一個子節點
  • READ:可以從一個節點讀取資料並展示子節點
  • WRITE:可以設定一個節點的資料
  • DELETE:可以刪除一個子節點
  • ADMIN:可以設定許可權