Zookeeper 分佈式服務框架是 Apache Hadoop 的一個子項目,它主要是用來解決分佈式應用中常常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集羣管理、分佈式應用配置項的管理等。html
訪問http://zookeeper.apache.org/releases.html 並下載最新版本的ZooKeeper,這裏我使用的版本是3.4.8。java
下載完成後解壓縮。進入conf目錄,建立zoo.cfg配置文件(可複製已有的zoo_sample.cfg修改)。node
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/tmp/zookeeper clientPort=2181
說明一下幾個配置項的意義(initLimit和syncLimit暫時先無論,後面有說明):apache
保存配置文件,返回進入bin目錄,雙擊zkService.cmd啓動服務,控制檯看到以下啓動信息:設計模式
... 2017-05-04 16:42:38,924 [myid:] - INFO [main:ZooKeeperServer@787] - tickTime set to 2000 2017-05-04 16:42:38,924 [myid:] - INFO [main:ZooKeeperServer@796] - minSessionTimeout set to -1 2017-05-04 16:42:38,925 [myid:] - INFO [main:ZooKeeperServer@805] - maxSessionTimeout set to -1 2017-05-04 16:42:38,993 [myid:] - INFO [main:NIOServerCnxnFactory@89] - binding to port 0.0.0.0/0.0.0.0:2181
再雙擊zkCli.cmd啓動客戶端,控制檯顯示以下信息:api
... WATCHER:: [zk: localhost:2181(CONNECTED) 0] WatchedEvent state:SyncConnected type:None path:null
ZooKeeper客戶端用於與ZooKeeper服務器進行交互,下面來進行簡單的操做。服務器
建立具備給定路徑的znode。session
語法數據結構
create /path data
示例:app
create /FirstZnode "myfirstzookeeper-app" Created /FirstZnode
使用參數能夠指定建立的節點模式CreateMode,CreateMode有下面幾種:
PERSISTENT:建立後只要不刪就永久存在
EPHEMERAL:會話結束年結點自動被刪除,EPHEMERAL結點不容許有子節點
SEQUENTIAL:節點名末尾會自動追加一個10位數的單調遞增的序號,同一個節點的全部子節點序號是單調遞增的
PERSISTENT_SEQUENTIAL:結合PERSISTENT和SEQUENTIAL
EPHEMERAL_SEQUENTIAL:結合EPHEMERAL和SEQUENTIAL
默認狀況下,全部znode都是持久的。
建立Sequential znode,請添加-s標誌,語法以下
create -s /path data
示例:
[zk: localhost:2181(CONNECTED) 1] create -s /FirstZnode "second-data" Created /FirstZnode0000000006
建立Ephemeral Znode,請添加-e標誌,語法以下
create -e /path data
示例:
[zk: localhost:2181(CONNECTED) 2] create -e /SecondZnode "Ephemeral-data" Created /SecondZnode
當客戶端鏈接丟失時,臨時znode將被刪除。下面咱們會經過退出ZooKeeper客戶端,而後從新打開zkCli來驗證。
獲取znode的數據,包括數據上次修改的時間,修改的位置等其餘相關信息。
語法
get /path
示例:
[zk: localhost:2181(CONNECTED) 3] get /FirstZnode myfirstzookeeper-app cZxid = 0x151 ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x151 mtime = Thu May 04 17:02:34 CST 2017 pZxid = 0x151 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 20 numChildren = 0
訪問Sequential znode,必須輸入znode的完整路徑,語法以下
get /path00000000xx
示例:
[zk: localhost:2181(CONNECTED) 4] get /FirstZnode0000000006 second-data cZxid = 0x152 ctime = Thu May 04 17:10:34 CST 2017 mZxid = 0x152 mtime = Thu May 04 17:10:34 CST 2017 pZxid = 0x152 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 11 numChildren = 0
這裏咱們來驗證Ephemeral Znode。
先獲取一次:
[zk: localhost:2181(CONNECTED) 5] get /SecondZnode Ephemeral-data cZxid = 0x153 ctime = Thu May 04 17:13:21 CST 2017 mZxid = 0x153 mtime = Thu May 04 17:13:21 CST 2017 pZxid = 0x153 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x15bd2a018370000 dataLength = 14 numChildren = 0
關閉zkCli命令行窗口,等待40s,重啓客戶端,再次嘗試獲取:
get /SecondZnode Node does not exist: /SecondZnode
能夠看到臨時znode已經不存在了,而永久節點是存在的:
[zk: localhost:2181(CONNECTED) 1] get /FirstZnode myfirstzookeeper-app cZxid = 0x151 ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x151 mtime = Thu May 04 17:02:34 CST 2017 pZxid = 0x151 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 20 numChildren = 0
這裏說一下,客戶端鏈接斷了以後,ZK不會立刻移除臨時數據,只有當SESSIONEXPIRED以後,纔會把這個會話創建的臨時數據移除。
而SESSIONEXPIRED是取決於客戶端和服務端兩方面的。
zk server端timeout參數:
咱們配置文件的tickTime爲2000,因此這裏服務端最大超時時間爲20*2000ms = 40s。
zk client端timeout參數:這裏cmd命令行不清楚怎麼設置,可是應該也有默認的值,不過默認值也不知道= =!。
查看zookeeper源碼可知,服務端拿到客戶端的超時時間後,是會作一些判斷的,客戶端超時時間實際限制在{2*tickeTime, 20*tickTime}範圍內。假設客戶端設置了timeout爲100s,實際40s就已經超時了;相似若是客戶端設置timeout爲1s,也要等到4s才超時。
回到咱們的測試,創建臨時節點,若是關閉客戶端後馬上(4s內)鏈接上,臨時節點仍然存在,而等待40s後session必定過時,臨時節點就被刪除了。
設置指定znode的數據。
語法
set /path data-updated
示例:
[zk: localhost:2181(CONNECTED) 4] set /FirstZnode "myfirstzookeeper-app1.1" cZxid = 0x151 ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x15d mtime = Thu May 04 17:46:18 CST 2017 pZxid = 0x151 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 0
查看結果:
[zk: localhost:2181(CONNECTED) 5] get /FirstZnode myfirstzookeeper-app1.1 cZxid = 0x151 ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x15d mtime = Thu May 04 17:46:18 CST 2017 pZxid = 0x151 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 0
當指定的znode或znode的子數據更改時,watch會顯示通知。
語法
get /path [watch] 1
示例:
[zk: localhost:2181(CONNECTED) 6] get /FirstZnode 1 myfirstzookeeper-app1.1 cZxid = 0x151 ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x15d mtime = Thu May 04 17:46:18 CST 2017 pZxid = 0x151 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 0
設置了監聽的節點被修改後,輸出會稍微不一樣,帶有watch信息
[zk: localhost:2181(CONNECTED) 7] set /FirstZnode "myfirstzookeeper-app1.2" WATCHER:: cZxid = 0x151 WatchedEvent state:SyncConnected type:NodeDataChanged path:/FirstZnode ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x15e mtime = Thu May 04 17:52:14 CST 2017 pZxid = 0x151 cversion = 0 dataVersion = 2 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 0
建立子節點相似於建立新的znode。惟一的區別是子節點znode的路徑包含了父路徑。
語法
create /parentpath/childpath data
示例:
[zk: localhost:2181(CONNECTED) 8] create /FirstZnode/Child1 "firstchildren" Created /FirstZnode/Child1 [zk: localhost:2181(CONNECTED) 9] create /FirstZnode/Child2 "secondchildren" Created /FirstZnode/Child2
查看znode全部的子節點。
語法
ls /path
示例:
[zk: localhost:2181(CONNECTED) 10] ls /FirstZnode [Child2, Child1]
查看根目錄下全部節點:
[zk: localhost:2181(CONNECTED) 11] ls / [dubbo, FirstZnode0000000006, zookeeper, FirstZnode]
查看指定znode的元數據。包含詳細信息,如時間戳,版本號,ACL,數據長度和子節點znode。
語法
stat /path
示例:
[zk: localhost:2181(CONNECTED) 12] stat /FirstZnode cZxid = 0x151 ctime = Thu May 04 17:02:34 CST 2017 mZxid = 0x15e mtime = Thu May 04 17:52:14 CST 2017 pZxid = 0x160 cversion = 2 dataVersion = 2 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 2
刪除指定的znode及其全部子節點。
語法
rmr /path
示例:
[zk: localhost:2181(CONNECTED) 13] rmr /FirstZnode [zk: localhost:2181(CONNECTED) 14] get /FirstZnode Node does not exist: /FirstZnode
還有一種刪除,只能刪除沒有子節點的節點,語法:
delete /path
示例:
[zk: localhost:2181(CONNECTED) 17] create /myZnode "mydata" Created /myZnode [zk: localhost:2181(CONNECTED) 18] create /myZnode/child1 "child1data" Created /myZnode/child1 [zk: localhost:2181(CONNECTED) 19] delete /myZnode Node not empty: /myZnode [zk: localhost:2181(CONNECTED) 20] delete /myZnode/child1 [zk: localhost:2181(CONNECTED) 21] delete /myZnode [zk: localhost:2181(CONNECTED) 22] get /myZnode Node does not exist: /myZnode
簡單使用了zookeeper以後,咱們發現其數據模型有些像操做系統的文件結構,結構以下圖所示
Znode是ZooKeeper核心組件,ZooKeeper API提供了方法來操縱znode。
客戶端遵循如下步驟來與ZooKeeper進行清晰和乾淨的交互:
示例:
import java.io.IOException; import java.util.List; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; /** * Test Zookeeper * * @author tenny.peng */ public class TestZookeeper { public static void main(String[] args) { try { // 建立一個Zookeeper實例。param1:目標服務器地址和端口;param2:Session超時時間;param3:節點變化時的回調方法。 ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 500000, new Watcher() { // 監控全部被觸發的事件 public void process(WatchedEvent event) { // dosomething System.out.println("監聽事件: " + event.toString()); } }); // 建立一個節點。param1:節點路徑;param2:節點數據;param3:權限控制;這裏表示全部人均可以操做;param4:節點類型,這裏爲永久。 zk.create("/FirstZnode", "my first zookeeper app".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 查看一個節點是否存在。param1:znode路徑;param2:是否監測(watch)查看的節點,重載方法:可傳入自定義watch。 Stat stat = zk.exists("/FirstZnode", true); System.out.println("version: " + stat.getVersion()); // 獲取一個節點的數據。param1:znode路徑;param2:是否監測(watch),重載方法:可傳入自定義watch;param3:znode的元數據。 byte[] b = zk.getData("/FirstZnode", true, stat); String data = new String(b); System.out.println(data); // 修改一個節點的數據。param1:znode路徑;param2:節點數據;param3:znode當前的版本號,-1無視被修改的數據版本,直接改掉。每當數據更改時,ZooKeeper會更新znode的版本號。 zk.setData("/FirstZnode", "my first zookeeper app1.1".getBytes(), stat.getVersion()); stat = zk.exists("/FirstZnode", true); System.out.println("version: " + stat.getVersion()); b = zk.getData("/FirstZnode", true, stat); data = new String(b); System.out.println(data); // 建立子節點,和建立節點同樣。 zk.create("/FirstZnode/child1", "child1 data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.create("/FirstZnode/child2", "child2 data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 獲取子節點。param1:znode路徑;param2:是否監測(watch),重載方法:可傳入自定義watch。 List<String> children = zk.getChildren("/FirstZnode", true); for (int i = 0; i < children.size(); i++) { System.out.println(children.get(i)); } // 刪除節點,若有子節點必須先刪除子節點。param1:znode路徑;param2:znode的當前版本,-1的話直接刪除,無視版本。 zk.delete("/FirstZnode/child1", -1); zk.delete("/FirstZnode/child2", -1); zk.delete("/FirstZnode", -1); // 關閉session zk.close(); } catch (IOException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
結果:
監聽事件: WatchedEvent state:SyncConnected type:None path:null version: 0 my first zookeeper app 監聽事件: WatchedEvent state:SyncConnected type:NodeDataChanged path:/FirstZnode version: 1 my first zookeeper app1.1 child2 child1 監聽事件: WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/FirstZnode 監聽事件: WatchedEvent state:SyncConnected type:NodeDeleted path:/FirstZnode
Zookeeper 不只能夠單機提供服務,同時也支持多機組成集羣來提供服務。實際上 Zookeeper 還支持另一種僞集羣的方式,也就是能夠在一臺物理機上運行多個 Zookeeper 實例。
下面介紹僞集羣模式的安裝和配置:
tickTime=2000 initLimit=10 syncLimit=4 dataDir=D:/devsoft/zookeeperCluster/server1/data dataLogDir=D:/devsoft/zookeeperCluster/server1/log clientPort=2181 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890
說明一下集羣的幾個配置參數
啓動第一個server,這時會報大量錯誤。
2017-05-06 17:26:12,366 [myid:1] - INFO [QuorumPeer[myid=1]/0:0:0:0:0:0:0:0:218 1:FastLeaderElection@852] - Notification time out: 800
不要緊,由於如今集羣只起了1臺server,zookeeper服務器端起來會根據zoo.cfg的服務器列表發起選舉leader的請求,由於連不上其餘機器而報錯。
咱們再起第二個zookeeper服務,leader將會被選出,從而一致性服務開始可使用,這是由於3臺機器只要有2臺可用就能夠選出leader而且對外提供服務(2n+1臺機器,能夠容n臺機器掛掉)。
2017-05-06 17:26:39,573 [myid:2] - INFO [WorkerSender[myid=2]:QuorumPeer$Quorum Server@149] - Resolved hostname: 127.0.0.1 to address: /127.0.0.1
再啓動最後一個zookeeper服務。
2017-05-06 17:27:11,071 [myid:3] - INFO [QuorumPeer[myid=3]/0:0:0:0:0:0:0:0:218 3:FileTxnSnapLog@240] - Snapshotting: 0x700000000 to D:\devsoft\zookeeperCluster \server3\data\version-2\snapshot.700000000
開啓一個客戶端zkCli.cmd,能夠看到成功鏈接集羣中的master。
2017-05-06 17:27:11,071 [myid:3] - INFO [QuorumPeer[myid=3]/0:0:0:0:0:0:0:0:218 3:FileTxnSnapLog@240] - Snapshotting: 0x700000000 to D:\devsoft\zookeeperCluster \server3\data\version-2\snapshot.700000000
而後客戶端就能夠像以前單機同樣進行各類操做。
ls / [zookeeper] [zk: localhost:2181(CONNECTED) 1] create /testZnode "testdata" Created /testZnode [zk: localhost:2181(CONNECTED) 2] ls / [zookeeper, testZnode] [zk: localhost:2181(CONNECTED) 3] delete /testZnode [zk: localhost:2181(CONNECTED) 4] ls / [zookeeper]
Zookeeper 從設計模式角度來看,是一個基於觀察者模式設計的分佈式服務管理框架,它負責存儲和管理你們都關心的數據,而後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上註冊的那些觀察者作出相應的反應,從而實現集羣中相似 Master/Slave 管理模式。
下面詳細介紹這些典型的應用場景。
配置的管理在分佈式應用環境中很常見,例如同一個應用系統須要多臺 PC Server 運行,可是它們運行的應用系統的某些配置項是相同的,若是要修改這些相同的配置項,那麼就必須同時修改每臺運行這個應用系統的 PC Server,這樣很是麻煩並且容易出錯。
像這樣的配置信息徹底能夠交給 Zookeeper 來管理,將配置信息保存在 Zookeeper 的某個目錄節點中,而後將全部須要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,而後從 Zookeeper 獲取新的配置信息應用到系統中。
好比將APP1的全部配置配置到/APP1 znode下,APP1全部機器一啓動就對/APP1這個節點進行監控,而且實現回調方法Watcher,那麼在zookeeper上/APP1 znode節點下數據發生變化的時候,每一個機器都會收到通知,Watcher方法將會被執行,那麼應用再取下數據便可。
Zookeeper 可以很容易的實現集羣管理的功能,若有多臺 Server 組成一個服務集羣,那麼必需要一個「總管」知道當前集羣中每臺機器的服務狀態,一旦有機器不能提供服務,集羣中其它集羣必須知道,從而作出調整從新分配服務策略。一樣當增長集羣的服務能力時,就會增長一臺或多臺 Server,一樣也必須讓「總管」知道。
Zookeeper 不只可以幫你維護當前的集羣中機器的服務狀態,並且可以幫你選出一個「總管」,讓這個總管來管理集羣,這就是 Zookeeper 的另外一個功能 Leader Election。
它們的實現方式都是在 Zookeeper 上建立一個 EPHEMERAL 類型的目錄節點,而後每一個 Server 在它們建立目錄節點的父目錄節點上調用 getChildren(String path, boolean watch) 方法並設置 watch 爲 true,因爲是 EPHEMERAL 目錄節點,當建立它的 Server 死去,這個目錄節點也隨之被刪除,因此 Children 將會變化,這時 getChildren上的 Watch 將會被調用,因此其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是一樣的原理。
Zookeeper 如何實現 Leader Election,也就是選出一個 Master Server。和前面的同樣每臺 Server 建立一個 EPHEMERAL 目錄節點,不一樣的是它仍是一個 SEQUENTIAL 目錄節點,因此它是個 EPHEMERAL_SEQUENTIAL 目錄節點。之因此它是 EPHEMERAL_SEQUENTIAL 目錄節點,是由於咱們能夠給每臺 Server 編號,咱們能夠選擇當前是最小編號的 Server 爲 Master,假如這個最小編號的 Server 死去,因爲是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,因此當前的節點列表中又出現一個最小編號的節點,咱們就選擇這個節點爲當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。
經過命令行和API使用了zookeeper的基本功能。Zoopkeeper 提供了一套很好的分佈式集羣管理的機制,即基於層次型的目錄樹的數據結構,並對樹中的節點進行有效管理,從而能夠設計出多種多樣的分佈式的數據管理模型。
參考連接:http://www.w3cschool.cn/zookeeper/ http://www.blogjava.net/BucketLi/archive/2010/12/21/341268.html https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/