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發送給客戶端一個通知。更多信息能夠查看watches章節。
數據訪問:
每一個znode上存儲的數據的讀寫都是原子的,讀操做取出全部的和這個znode有關的全部數據,寫操做替換全部數據。每一個節點有一個訪問權限列表(ACL)限制誰能夠作這些事。
zookeeper沒有被設計成一個通常的數據庫,或者大型對象存儲。它管理協調數據,數據能夠是狀態,配置信息,集合點等的形式。各類各樣的數據有一個共同的屬性就是它們都很小:以千字節爲標準。
ZooKeeper客戶端和服務器有一個健康檢查來確保znodes的數據少於1M,可是數據平均應該更小。操做較大的數據將致使一些操做花費更多的時 間,而且會影響一些操做的延遲,由於在網絡和存儲媒介中移動更多的數據將須要額外的時間。若是須要存儲大數據,一般的處理是把數據存儲在一個大容量存儲系 統中,並把存儲位置的指針存儲到ZooKeeper上。
臨時節點:
zookeeper也有臨時節點的概念。這些znodes存活的時間和建立這個節點的會話有效期是同樣的。當會話結束,節點被刪除。由於這種臨時節點的特性,臨時節點不容許有子節點。
順序節點——惟一名稱:
當建立一個節點的時候,也能夠請求zookeeper在路徑後面增長一個自增的計數器。對父節點來講,這個計數器是惟一的。計數器是%010d的格式( 十進制整數輸出,寬度10位,不足前面補0;)——是一個十位數,好比:<path>0000000001。
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 結構
- 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客戶端庫將處理鏈接。尤爲是客戶端內部有方法來處理像「羊羣效應」之類的事情。僅僅在你被通知會話過時的時候去建立一個新的會 話。express
ZooKeeper集羣本身管理會話過時,而不是由客戶端管理。當ZK客戶端和一個集羣創建會話,它提供一個「超時時間」。這個值被集羣使用來決定 客戶端的會話是否過時。當集羣不能在指定的會話超時時間內從客戶端收到信息,過時發生。在會話過時期間,集羣將刪除由這個會話建立的全部的臨時節點,而且 當即通知鏈接的客戶端這個改變。此時,會話過時的客戶端依然和集羣式斷開的,它不會收到通知直到它能和集羣從新創建鏈接。這個客戶端將保持斷開狀態直到和 集羣的TCP鏈接從新創建,而且在這個時候,過時會話的監聽將會收到「會話過時」通知。
- 「connected」:會話被創建,而且客戶端能和集羣交流
- ……客戶端從集羣被分割
- 「disconnected」:客戶端與集羣丟失了聯繫
- ……時間流逝,在超時時間以後,集羣已經讓這個會話過時,而客戶端沒看到什麼,由於它已經從集羣斷開鏈接了
- ……時間流逝,客戶端恢復網絡和集羣聯通
- 「expired」:最後客戶端與集羣從新鏈接,而後收到過時的通知
ZooKeeper會話創建的另外一個參數是默認監聽器。當客戶端的一些狀態改變發生,監聽器會收到通知。好比若是客戶端丟失與服務端的鏈接,客戶端將會收到通知,或客戶端的會話到期等。這個監聽器應該考慮初始狀態到斷開鏈接。對於一個新的鏈接,第一個發給監聽器的事件就是會話鏈接事件。
客戶端經過發送請求保持會話存活。若是會話在一段時間內空閒將會致使會話超時,客戶端將會發送PING請求保持會話存活。這個PING請求不只僅讓 ZooKeeper服務端知道客戶端是存活的,並且讓客戶端檢查它的和ZooKeeper 服務端的鏈接也是存活的。PING的時間是足夠保守的合理時間,來發現死掉的鏈接和一個新的服務端從新鏈接。
一旦成功創建一個到服務端的鏈接,當客戶端發生connectionloss異常 時有兩種基本的狀況,在執行一個同步或者非同步的操做時:安全
- 應用調用一個操做,可是會話再也不存活。
- 當等待一個操做的時候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對監聽器的保證app
對於監聽器,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:id,scheme是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:能夠設置權限