本篇文章幫助開發者瞭解如何利用zookeeper的協調服務開發分佈式應用。文章講解了開發中涉及到的一些概念和實踐的內容。文章的前四部分討論了zookeeper的概念,瞭解這些概念使用者理解zookeeper如何工做以及如何利用它,後面四個部分講解的是編程實踐的內容。java
ZooKeeper有一個相似於分佈式文件系統的分層命名空間。空間中的節點既能夠包含數據也能夠關聯子節點,這個節點既像文件又像文件目錄。命名空間中的節點路徑不是相對路徑,是一個標準化的,絕對的,使用反斜扛分隔的路徑。路徑中能夠包含任何的unicode字符除了如下的限制:node
ZooKeeper命名空間中的節點被稱爲ZNode,Znode節點維持了一個包含了數據變動版本號和acl(訪問控制列表)變動版本號的狀態結構,狀態結構中還有一個時間戳字段。版本號和時間戳被用來進行緩存驗證和協調更新。ZNode節點數據發生變動的時候,版本號就會增長。數據庫
zookeeper客戶端能夠在ZNode上設置監控器,ZNode節點數據發生變化的時候,就會經過這些監控器觸發向客戶端發送通知的任務。express
每個ZNode的數據都是原子讀寫,也就是說讀和寫操做都是對整個ZNode節點的操做。爲了控制對ZNode的操做,ZNode維持了一個ACL(訪問控制列表)來控制什麼人具備操做權限。 zookeeper並無設計爲普通的數據庫存儲或者大對象存儲,相反他用來保存一些協調性數據例如配置信息,狀態信息或者位置信息等。這些協調性的數據通常都比較小,在1千字節之內。zookeeper客戶端和服務器的實現有一個健康檢查來檢查數據大小小於1M,可是實際的平均水平要遠小於這個值。節點數據較大會致使某些節點操做耗時較多致使處理延遲變大,若是必須存儲較大數據,考慮保存數據保存位置。apache
zookeeper有一個臨時節點的概念,臨時節點的生命週期與會話一致,隨會話建立而建立,隨會話關閉而被刪除。臨時節點不容許包含子節點。編程
ZNode節點建立的時候能夠在後面加一個路徑後面加一個計數器。對於父節點計數是惟一的。緩存
zookeeper在3.6版本後添加了一個容器節點的概念,容器節點被設計用於特殊的用途例如領袖選舉,鎖處理等。當容器節點中的最後一個節點被刪除後,容器節點將會被標誌位待刪除節點,在將來的某一個時間點會被服務器刪除。因爲上面提到的內容,在容器節點添加子節點的時候要檢查容器節點是否有子節點,若是沒有子節點須要重建容器節點再添加子節點。安全
zookeeper經過下面四種方式來追蹤時間:服務器
zookeeper每個狀態的改變都會受收一個稱爲Zxid的標誌,Zxid是一個全局性的屬性值,若是受到兩個請求,Zxid較小的請求要先於Zxid較大的請求。網絡
zookeeper的每次修改都會致使版本號的增長,znode有三種版本號:version (znode數據發生變動的版本號),cversion (znode子節點的變動版本號),以及aversion(ACL的變動版本號)
Ticks屬性被用來做爲時間基本單位,時間單位是毫秒。在多zookeeper服務器的時候,它被用來爲狀態上傳,會話超時,鏈接超時等時間的基本單位。
zookeeper除了時間戳,其餘時間都不是使用真實時間或者時鐘時間。
zookeeper客戶端經過java或者C語言綁定的方式創建一個句柄來鏈接zookeeper服務器。句柄的開始狀態是鏈接中狀態,若是和提供zookeeper服務的某一個服務器成功鏈接,句柄的狀態會變爲已鏈接。若是客戶端和服務器處於鏈接狀態的時候,客戶端主動關閉會話或者出現不可恢復異常的時候,句柄的狀態會變爲關閉狀態。
爲了建立客戶端和服務器會話,應用須要傳遞一個逗號分隔的主機列表,開始建立鏈接的時候,客戶端會從主機列表中隨機選擇一臺進行鏈接,若是鏈接失敗將會自動選擇下一臺進行鏈接,直到鏈接成功。
3.2.0版本新增特性:
支持在鏈接串後面添加一個根路徑後綴,添加後綴後執行的命令都是相對於此根路徑的操做。例如使用以下的鏈接串"127.0.0.1:4545/app/a" 或者 "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" ,運行命令對/foo/bar進行設置,修改等處理的時候,實際處理的是/app/a/foo/bar路徑。在多終端環境,每一個用戶都有本身的一個單獨的根路徑的時候,這個特性很是有用。
當客戶端和服務器成功建立鏈接的時候,服務器會給客戶端指定一個64位的字符串來標誌這個會話。當客戶端鏈接其餘的服務器的時候,他會在建立鏈接的時候將這個64位的會話id傳遞給服務器。基於安全考慮,防止有人惡意仿冒已創建會話的客戶端,建立鏈接的時候伴隨會話id下發的還有一個祕鑰,這個祕鑰會被用於檢查重連會話是不是正確的。當客戶端重連服務器的時候,客戶端會攜帶會話id和祕鑰進行會話重連。
客戶端建立會話有一個超時機制控制,超時時間要大於兩倍的tickTime並小於二十倍的tickTime。而且支持經過協商設置這個超時時間。
客戶端從zookeeper集羣中斷開的時候,它將會檢索主機列表嘗試進行重連。若是重連成功句柄的狀態會從新變回已鏈接狀態,若是重連超時,那麼句柄會變成超時狀態。zookeeper已經實現自動重連機制因此不推薦爲重連定義單獨的對象,若是接收到超時的提示的時候,直接重連會話就能夠了。
會話超時是由服務器集羣控制的,若是集羣在超時時間內沒有接收到客戶端的心跳就會認爲會話超時,就會將當前會話的全部臨時節點刪除,並通知設置了監聽器的客戶端這個變化。客戶端會一直保持斷開狀態直到客戶端從新鏈接服務器,監控器會通知客戶端會話超時。
ZooKeeper的getData(), getChildren()和exists() 讀操做能夠設置監控器來監控數據的變化。ZooKeeper對監控器的定義是監控的數據發生變化的一次性的發送給客戶端的事件。對於zookeeper中的監控器須要注意以下三點:
數據發生變化的時候,監控事件將會觸發,可是若是已經觸發過而且沒有再次設置監控器,數據後續發生變化是不會觸發監控事件的。
監控事件是異步的,數據發送變化的時候向客戶端發送通知的事件。因爲可能存在網絡延遲或者其餘不可測的緣由致使不一樣客戶端在不一樣的時間接收到監控事件並響應事件。爲了保證數據的一致性,客戶端只會對第一次接收到的監聽事件作處理,後續若是是同一個事件的話會被忽略。
監控事件主要分爲兩類,一種是監控數據變化的稱爲數據監控器,一種是監控子節點變化的稱爲子節點監控器。getData()和exists()方法會設置數據監控器,getChildren()會設置子節點監控器。
監控事件被保存在和客戶端鏈接的zookeeper服務器中,這能夠保證事件是輕量級的,方便維護和分發。若是客戶端從新鏈接一個新的zookeeper服務器,若是須要的話這個監控事件將會經過任何一個會話事件觸發註冊。可是須要注意的是若是客戶端斷連的時候,數據發生變化,這個監控事件將會遺失。
zookeeper使用ACL進行訪問控制,ACL的實現方式相似於Unix系統的權限位來控制對znode的各類操做以及範圍。ACL中保存的是id集合以及對應的權限。ACL的控制是針對特定節點的,不可以遞歸賦予。
zookeeper支持可插入的認證方案。id列表經過scheme:id的形式指定,scheme表明的是一種認證方案,id表示的容許訪問的id。例如ip:172.16.16.1指的就是支持ip地址爲172.16.16.1的主機訪問znode。
客戶端鏈接上zookeeper服務器的時候,zookeeper服務器會將客戶端與其具備的id關聯。當客戶端訪問znode的時候,znode會經過ACL來校驗客戶端是否能夠訪問以及訪問權限。ACL列表是由(scheme:expression, perms)形式的結構組成,經過scheme指定具體的認證方案。例如(ip:19.22.0.0/16, READ)容許19.22.0.0/16網段的主機對znode有讀權限。
zookeeper支持的ACL權限以下:
zookeeper支持的內存ACL方案以下:
world:有一個惟一的id,anyone,表示任何人均可以訪問。
auth:不須要id,表示說有認證過的人均可以訪問。
digest:使用username:password的字符串進行md5的哈希來做爲ACL的id。認證是經過發送username:password明文來進行的。若是在ACL表達式中使用須要使用username:base64的格式(base64是密碼經過SHA1加密)
ip:使用客戶端的ip來進行認證,格式爲addr/bits。
zookeeper支持在不一樣的環境中使用不一樣的認證方案,所以它採用的是一種可插拔的認證框架。內置的認證方案也是使用這種可插拔的框架。
爲了理解認證框架是如何工做的,你須要瞭解兩種主要的認證操做。框架第一步須要驗證客戶端,這個是在客戶端鏈接服務器的時候驗證的。第二個是框架經過查找ACL響應客戶端。ACL記錄爲<idspec, permissions> 的結構對。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方法返回認證插件標識,因爲咱們支持多種認證方式,服務器經過不一樣的scheme獲取到對應的認證方法。
handleAuthentication方法在客戶端向服務器發起認證的時候觸發,經過getScheme獲取對應的認證方法,將傳遞過來的認證信息進行認證,若是認證失敗會向客戶端返回錯誤信息。
認證插件會在設置和使用ACL的時候被調用,當給znode設置ACL的時候zookeeper服務器將會將ACL的id值傳遞給isValid(String id)方法。經過插件來校驗id是不是一個正確的形式。
zookeeper在校驗ACL的時候會調用matches(String id, String aclExpr)方法,這個方法須要匹配客戶端的認證信息和ACL中的記錄。zookeeper會檢索全部的ACL記錄的scheme,若是有和客戶端攜帶的scheme一致的記錄就會調用matches(String id, String aclExpr)方法,使用插件本身的實現對認證信息進行校驗。
zookeeper提供了兩種內存的認證插件:ip和digest。額外的插件能夠經過系統屬性添加,zookeeper啓動的時候會查詢zookeeper.authProvider屬性來獲取認證插件,並將插件名解析爲對應的插件實現類名。這些屬性能夠經過-Dzookeeeper.authProvider.X=com.f.MyAuth或者服務器配置文件設置
authProvider.1=com.f.MyAuth authProvider.2=com.f.MyAuth2
須要注意的是後綴必須是惟一的,若是屬性名有重複只有一個是有效的。
zookeeper是一個高性能,高拓展性的服務。它的讀寫操做都被設計的很是快而且讀操做更快。讀操做能夠如此快是由於zookeeper提供舊數據服務並提供一致性保障:
客戶端對系統的視圖被保證在某個時間段內(數十秒)是最新的,每個系統的改變都在這個事件段內更新到客戶端。
zookeeper的java綁定有兩個包構成org.apache.zookeeper和org.apache.zookeeper.data。其餘的包爲內部使用或者服務器實現使用。org.apache.zookeeper.data包由生成的類組成僅做爲容器。
ZooKeeper的java客戶端主要使用的類是ZooKeeper類,它的兩個構造方法區別在於可選的會話id和密碼。zookeeper支持進程內的會話恢復,java程序能夠保存會話id和密碼到一個穩態存儲,在服務重啓的時候能夠恢復會話。
當zookeeper對象建立後,有兩個線程會同時建立(一個IO線程,一個事件線程)。全部的IO都發生在IO線程(使用java NIO),全部的事件毀掉都發生在事件線程。會話維護例如重連,心跳在IO線程中已經處理。同步方法的響應也在IO線程處理。全部的異步事件處理,監控事件都在線程線程中處理。因爲這種設計咱們須要注意以下內容: