閱讀626 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《Apache Zookeeper 官方文檔》-4 ZooKeeper編程指南

簡介

對於想要利用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:可以設置權限
  • 轉載自 並發編程網 - ifeve.com

最後更新:2017-05-19 17:31:48

  上一篇:go  《雲數據管理:挑戰與機遇》導讀
  下一篇:go  MariaDB 源碼調試