zookeeper入門與實戰
來自: http://my.oschina.net/yafeiok/blog/618835
1. zookeeper介紹
ZooKeeper是一個為分布式應用所設計的分布的、開源的協調服務,它主要是用來解決分布式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀 態同步服務、集群管理、分布式應用配置項的管理等,簡化分布式應用協調及其管理的難度,提供高性能的分布式服務。Zookeeper的目標就是封裝好復雜 易出錯的關鍵服務,將簡單易用的接口和性能高效、功能穩定的系統提供給用戶。ZooKeeper本身可以以Standalone模式安裝運行,不過它的長 處在于通過分布式ZooKeeper集群(一個Leader,多個Follower),基于一定的策略來保證ZooKeeper集群的穩定性和可用性,從 而實現分布式應用的可靠性。
最新的版本可以在官網http://zookeeper.apache.org/releases.html來下載zookeeper的最新版本,我下載的是zookeeper-3.4.6.tar.gz,下面將從單機模式和集群模式兩個方面介紹 Zookeeper 的安裝和配置。
1.1 zookeeper的主要功能
組管理服務、分布式配置服務、分布式同步服務、分布式命名服務
1.2 Zookeeper的架構
1.3 Zookeeper的特點
特點 | 說明 |
---|---|
最終一致性 | 為客戶端展示同一個視圖,這是zookeeper里面一個非常重要的功能 |
可靠性 | 如果消息被到一臺服務器接受,那么它將被所有的服務器接受。 |
實時性 | Zookeeper不能保證兩個客戶端能同時得到剛更新的數據,如果需要最新數據,應該在讀數據之前調用sync()接口。 |
獨立性 | 各個Client之間互不干預 |
原子性 | 更新只能成功或者失敗,沒有中間狀態。 |
順序性 | 所有Server,同一消息發布順序一致。 |
1.4 zookeeper的工作原理
1.每個Server在內存中存儲了一份數據;
2.Zookeeper啟動時,將從實例中選舉一個leader(Paxos協議)
3.Leader負責處理數據更新等操作(Zab協議);
4.一個更新操作成功,當且僅當大多數Server在內存中成功修改數據。
1.5 zookeeper中的幾個重要角色
角色名 | 描述 |
---|---|
領導者(Leader) | 領導者負責進行投票的發起和決議,更新系統狀態,處理寫請求 |
跟隨者(Follwer) | Follower用于接收客戶端的讀寫請求并向客戶端返回結果,在選主過程中參與投票 |
觀察者(Observer) | 觀察者可以接收客戶端的讀寫請求,并將寫請求轉發給Leader,但Observer節點不參與投票過程,只同步leader狀態,Observer的目的是為了,擴展系統,提高讀取速度。 在3.3.0版本之后,引入Observer角色的原因: Zookeeper需保證高可用和強一致性; |
客戶端(Client) | 執行讀寫請求的發起方 |
1.6 zookeeper集群的數目一般為奇數的原因
Leader選舉算法采用了Paxos協議;
Paxos核心思想:當多數Server寫成功,則任務數據寫成功
如果有3個Server,則兩個寫成功即可;
如果有4或5個Server,則三個寫成功即可。
Server數目一般為奇數(3、5、7)
如果有3個Server,則最多允許1個Server掛掉;
如果有4個Server,則同樣最多允許1個Server掛掉
由此,我們看出3臺服務器和4臺服務器的的容災能力是一樣的,所以
為了節省服務器資源,一般我們采用奇數個數,作為服務器部署個數。
1.7 zookeeper的數據模型
基于樹形結構的命名空間,與文件系統類似
節點(znode)都可以存數據,可以有子節點
節點不支持重命名
數據大小不超過1MB(可配置)
數據讀寫要保證完整性
層次化的目錄結構,命名符合常規文件系統規范;
每個節點在zookeeper中叫做znode,并且其有一個唯一的路徑標識;
節點Znode可以包含數據和子節點(EPHEMERAL類型的節點不能有子節點);
Znode中的數據可以有多個版本,比如某一個路徑下存有多個數據版本,那么查詢這個路徑下的數據需帶上版本;
客戶端應用可以在節點上設置監視器(Watcher);
節點不支持部分讀寫,而是一次性完整讀寫。
Znode有兩種類型,短暫的(ephemeral)和持久的(persistent);
Znode的類型在創建時確定并且之后不能再修改;
短暫znode的客戶端會話結束時,zookeeper會將該短暫znode刪除,短暫znode不可以有子節點;
持久znode不依賴于客戶端會話,只有當客戶端明確要刪除該持久znode時才會被刪除;
Znode有四種形式的目錄節點,PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL。
1.8 zookeeper的主要應用場景
場景 | 介紹 | 實現思路 |
---|---|---|
統一命名服務 |
分布式環境下,經常需要對應用/服務進行統一命名,便于識別不同服務; 類似于域名與ip之間對應關系,域名容易記住; 通過名稱來獲取資源或服務的地址,提供者等信息 按照層次結構組織服務/應用名稱 可將服務名稱以及地址信息寫到Zookeeper上,客戶端通過Zookeeper獲取可用服務列表類 |
|
配置管理服務 | 分布式環境下,配置文件管理和同步是一個常見問題; |
|
集群管理 | 分布式環境中,實時掌握每個節點的狀態是必要的; |
在 Zookeeper 上創建一個 EPHEMERAL 類型的目錄節點,然后每個 Server 在它們創建目錄節點的父目錄節點上調用getChildren(String path, boolean watch) 方法并設置 watch 為 true,由于是 EPHEMERAL 目錄節點,當創建它的 Server 死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時 getChildren上的 Watch 將會被調用,所以其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是同樣的原理。 |
分布式通知和協調 | 分布式環境中,經常存在一個服務需要知道它所管理的子服務的狀態; |
|
分布式鎖 | Zookeeper是強一致的; |
實現方式是需要獲得鎖的 Server 創建一個 EPHEMERAL_SEQUENTIAL 目錄節點,然后調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點,如果正是自己創建的,那么它就獲得了這個鎖,如果不是那么它就調用 exists(String path, boolean watch) 方法并監控 Zookeeper 上目錄節點列表的變化,一直到自己創建的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。 |
分布式隊列 | 兩種隊列; |
同步隊列實現思路:
創建一個父目錄 /synchronizing,每個成員都監控標志(Set Watch)位目錄 /synchronizing/start 是否存在,然后每個成員都加入這個隊列,加入隊列的方式就是創建 /synchronizing/member_i 的臨時目錄節點,然后每個成員獲取 / synchronizing 目錄的所有目錄節點,也就是 member_i。判斷 i 的值是否已經是成員的個數,如果小于成員個數等待 /synchronizing/start 的出現,如果已經相等就創建 /synchronizing/start。 FIFO 隊列實現思路: 實現的思路也非常簡單,就是在特定的目錄下創建 SEQUENTIAL 類型的子目錄 /queue_i,這樣就能保證所有成員加入隊列時都是有編號的,出隊列時通過 getChildren( ) 方法可以返回當前所有的隊列中的元素,然后消費其中最小的一個,這樣就能保證 FIFO。
|
2. zookeeper單節點(Standalone)模式
sunguoli@sunguolideMacBook-Pro:~/zookeeper$ tar -zxvf zookeeper-3.4.6.tar.gz sunguoli@sunguolideMacBook-Pro:~/zookeeper$ cd zookeeper-3.4.6 sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ ls -al total 3088 drwxr-xr-x@ 23 sunguoli staff 782 9 28 15:15 . drwxr-xr-x 9 sunguoli staff 306 9 28 15:04 .. -rw-r--r--@ 1 sunguoli staff 80776 2 20 2014 CHANGES.txt -rw-r--r--@ 1 sunguoli staff 11358 2 20 2014 LICENSE.txt -rw-r--r--@ 1 sunguoli staff 170 2 20 2014 NOTICE.txt -rw-r--r--@ 1 sunguoli staff 1585 2 20 2014 README.txt -rw-r--r--@ 1 sunguoli staff 1770 2 20 2014 README_packaging.txt drwxr-xr-x@ 11 sunguoli staff 374 9 29 15:50 bin -rw-r--r--@ 1 sunguoli staff 82446 2 20 2014 build.xml drwxr-xr-x@ 9 sunguoli staff 306 9 29 15:48 conf drwxr-xr-x@ 10 sunguoli staff 340 2 20 2014 contrib drwxr-xr-x@ 22 sunguoli staff 748 2 20 2014 dist-maven drwxr-xr-x@ 49 sunguoli staff 1666 2 20 2014 docs -rw-r--r--@ 1 sunguoli staff 3375 2 20 2014 ivy.xml -rw-r--r--@ 1 sunguoli staff 1953 2 20 2014 ivysettings.xml drwxr-xr-x@ 11 sunguoli staff 374 2 20 2014 lib drwxr-xr-x@ 5 sunguoli staff 170 2 20 2014 recipes drwxr-xr-x@ 11 sunguoli staff 374 2 20 2014 src -rw-r--r--@ 1 sunguoli staff 1340305 2 20 2014 zookeeper-3.4.6.jar -rw-r--r--@ 1 sunguoli staff 836 2 20 2014 zookeeper-3.4.6.jar.asc -rw-r--r--@ 1 sunguoli staff 33 2 20 2014 zookeeper-3.4.6.jar.md5 -rw-r--r--@ 1 sunguoli staff 41 2 20 2014 zookeeper-3.4.6.jar.sha1 -rw-r--r-- 1 sunguoli staff 23359 9 29 18:28 zookeeper.out |
2.1 修改配置文件
我要把zookeeper的數據放在/Users/sunguoli/zookeeper/data這個文件夾里面,另外zookeeper默認使用的配置文件時zoo.cfg,所以要把zoo_sample.cfg cp到zoo.cfg
sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ mkdir /Users/sunguoli/zookeeper/data sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ cp conf/zoo_sample.cfg conf/zoo.cfg sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ vi conf/zoo.cfg 1 # The number of milliseconds of each tick 2 tickTime=2000 3 # The number of ticks that the initial 4 # synchronization phase can take 5 initLimit=10 6 # The number of ticks that can pass between 7 # sending a request and getting an acknowledgement 8 syncLimit=5 9 # the directory where the snapshot is stored. 10 # do not use /tmp for storage, /tmp here is just 11 # example sakes. 12 dataDir=/Users/sunguoli/zookeeper/data 13 # the port at which the clients will connect 14 clientPort=2181 15 # the maximum number of client connections. 16 # increase this if you need to handle more clients 17 #maxClientCnxns=60 18 # 19 # Be sure to read the maintenance section of the 20 # administrator guide before turning on autopurge. 21 # 22 # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance 23 # 24 # The number of snapshots to retain in dataDir 25 #autopurge.snapRetainCount=3 26 # Purge task interval in hours 27 # Set to "0" to disable auto purge feature 28 #autopurge.purgeInterval=1 |
各個配置項的解釋:
tickTime:這個時間是作為 Zookeeper 服務器之間或客戶端與服務器之間維持心跳的時間間隔,也就是每個 tickTime 時間就會發送一個心跳。
initLimit:這個配置項是用來配置 Zookeeper 接受客戶端(這里所說的客戶端不是用戶連接 Zookeeper 服務器的客戶端,而是 Zookeeper 服務器集群中連接到 Leader 的 Follower 服務器)初始化連接時最長能忍受多少個心跳時間間隔數。當已經超過 10 個心跳的時間(也就是 tickTime)長度后 Zookeeper 服務器還沒有收到客戶端的返回信息,那么表明這個客戶端連接失敗。總的時間長度就是 5*2000=10 秒
syncLimit:這個配置項標識 Leader 與 Follower 之間發送消息,請求和應答時間長度,最長不能超過多少個 tickTime 的時間長度,總的時間長度就是 2*2000=4 秒
dataDir:顧名思義就是 Zookeeper 保存數據的目錄,默認情況下,Zookeeper 將寫數據的日志文件也保存在這個目錄里。我們上文配置的目錄是:/Users/sunguoli/zookeeper/data
clientPort:這個端口就是客戶端連接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端口,接受客戶端的訪問請求。
2.2 啟動zookeeper
sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh start JMX enabled by default Using config: /Users/sunguoli/zookeeper/zookeeper-3.4.6/bin/../conf/zoo.cfg Starting zookeeper ... STARTED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ zkServer.sh status JMX enabled by default Using config: /Users/sunguoli/zookeeper/zookeeper-3.4.6/bin/../conf/zoo.cfg Mode: standalone |
從命令行中看到,zookeeper已經啟動了,模式是standalone,使用的配置文件是zoo.cfg
2.3 連接zookeeper
sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkCli.sh Connecting to localhost:2181 ... Welcome to ZooKeeper! ... WATCHER:: WatchedEvent state:SyncConnected type:None path:null #用man或者help查看下有哪些可用的命令,我們看到了很多熟悉的命令 [zk: localhost:2181(CONNECTED) 0] man ZooKeeper -server host:port cmd args connect host:port get path [watch] ls path [watch] set path data [version] rmr path delquota [-n|-b] path quit printwatches on|off create [-s] [-e] path data acl stat path [watch] close ls2 path [watch] history listquota path setAcl path acl getAcl path sync path redo cmdno addauth scheme auth delete path [version] setquota -n|-b val path [zk: localhost:2181(CONNECTED) 1] help ZooKeeper -server host:port cmd args connect host:port get path [watch] ls path [watch] set path data [version] rmr path delquota [-n|-b] path quit printwatches on|off create [-s] [-e] path data acl stat path [watch] close ls2 path [watch] history listquota path setAcl path acl getAcl path sync path redo cmdno addauth scheme auth delete path [version] setquota -n|-b val path #ZooKeeper的結構,很像是目錄結構,ls一下,看到了一個默認的節點zookeeper [zk: localhost:2181(CONNECTED) 0] ls / [zookeeper] #創建一個新的節點,/node, 值是helloword [zk: localhost:2181(CONNECTED) 1] create /node helloword Created /node #再看一下,恩,多了一個我們新建的節點/node,和zookeeper都是在/根目錄下的。 [zk: localhost:2181(CONNECTED) 2] ls / [node, zookeeper] #看看節點的值是啥?還真是我們設置的helloword,還顯示了創建時間,修改時間,version,長度,children個數等。 [zk: localhost:2181(CONNECTED) 3] get /node helloword cZxid = 0x32 ctime = Wed Sep 30 15:06:10 CST 2015 mZxid = 0x32 mtime = Wed Sep 30 15:06:10 CST 2015 pZxid = 0x32 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 9 numChildren = 0 #修改值,看看,創時間沒變,修改時間變了,長度變了,數據版本值變了。 [zk: localhost:2181(CONNECTED) 4] set /node helloword! cZxid = 0x32 ctime = Wed Sep 30 15:06:10 CST 2015 mZxid = 0x33 mtime = Wed Sep 30 15:06:55 CST 2015 pZxid = 0x32 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0 #再看看 [zk: localhost:2181(CONNECTED) 5] get /node helloword! cZxid = 0x32 ctime = Wed Sep 30 15:06:10 CST 2015 mZxid = 0x33 mtime = Wed Sep 30 15:06:55 CST 2015 pZxid = 0x32 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0 #給他創建一個子節點,值testchild [zk: localhost:2181(CONNECTED) 6] create /node/childnode testchild Created /node/childnode #看看根目錄,恩,沒變 [zk: localhost:2181(CONNECTED) 7] ls / [node, zookeeper] #命令輸錯鳥。。。⊙﹏⊙b汗 [zk: localhost:2181(CONNECTED) 8] ls /node/ Command failed: java.lang.IllegalArgumentException: Path must not end with / character #看一看 [zk: localhost:2181(CONNECTED) 9] ls /node [childnode] #childdren的數目變了 [zk: localhost:2181(CONNECTED) 10] get /node helloword! cZxid = 0x32 ctime = Wed Sep 30 15:06:10 CST 2015 mZxid = 0x33 mtime = Wed Sep 30 15:06:55 CST 2015 pZxid = 0x34 cversion = 1 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 1 #再建一個根節點 [zk: localhost:2181(CONNECTED) 11] create /test test Created /test #多了個根節點哦~ [zk: localhost:2181(CONNECTED) 12] ls / [node, test, zookeeper] #刪 [zk: localhost:2181(CONNECTED) 13] delete /test #偶,不見了 [zk: localhost:2181(CONNECTED) 14] ls / [node, zookeeper] #看看默認的節點有啥? [zk: localhost:2181(CONNECTED) 15] ls /zookeeper [quota] [zk: localhost:2181(CONNECTED) 16] get /zookeeper cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x0 cversion = -1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 1 [zk: localhost:2181(CONNECTED) 17] get /zookeeper/quota cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x0 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 0 #退出 [zk: localhost:2181(CONNECTED) 18] quit Quitting... 2015-09-30 15:08:40,639 [myid:] - INFO [main:ZooKeeper@684] - Session: 0x1501d03ae510003 closed 2015-09-30 15:08:40,639 [myid:] - INFO [main-EventThread:ClientCnxn$EventThread@512] - EventThread shut down sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ |
2.4 stop zookeeper
sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh stop JMX enabled by default Using config: /Users/sunguoli/zookeeper/zookeeper-3.4.6/bin/../conf/zoo.cfg Stopping zookeeper ... STOPPED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ |
3. zookeeper偽分布式集群模式
上面體驗了單節點模式,我們來體驗下偽分布式模式,在一臺機器上跑多個zookeeper節點。當然偽分布式模式是區別于完全分布式模式有多臺機器,每天機器上一個zookeeper實例。
3.1 配置zookeeper
我們來建一個3個節點的zookeeper偽分布式集群
#建3個數據目錄 sunguoli@sunguolideMacBook-Pro:~/zookeeper$ mkdir /Users/sunguoli/zookeeper/data1 sunguoli@sunguolideMacBook-Pro:~/zookeeper$ mkdir /Users/sunguoli/zookeeper/data2 sunguoli@sunguolideMacBook-Pro:~/zookeeper$ mkdir /Users/sunguoli/zookeeper/data3 #分別新建myid文件 sunguoli@sunguolideMacBook-Pro:~/zookeeper$ echo "1" > data1/myid sunguoli@sunguolideMacBook-Pro:~/zookeeper$ echo "2" > data2/myid sunguoli@sunguolideMacBook-Pro:~/zookeeper$ echo "3" > data3/myid #修改配置文件,主要是dataDir, clientPort, server #zk1 sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ cat conf/zk1.cfg # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=/Users/sunguoli/zookeeper/data1 # the port at which the clients will connect clientPort=2181 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890 #zk2 sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ cat conf/zk2.cfg # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=/Users/sunguoli/zookeeper/data2 # the port at which the clients will connect clientPort=2182 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890 #zk3 sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ cat conf/zk3.cfg # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=/Users/sunguoli/zookeeper/data3 # the port at which the clients will connect clientPort=2183 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 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.A=B:C:D:其中 A 是一個數字,表示這個是第幾號服務器;B 是這個服務器的 ip 地址;C 表示的是這個服務器與集群中的 Leader 服務器交換信息的端口;D 表示的是萬一集群中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader,而這個端口就是用來執行選舉時服務器相互通信的端口。如果是偽集群的配置方式,由于 B 都是一樣,所以不同的 Zookeeper 實例通信端口號不能一樣,所以要給它們分配不同的端口號。
除了修改 zoo.cfg 配置文件,集群模式下還要配置一個文件 myid,這個文件在 dataDir 目錄下,這個文件里面就有一個數據就是 A 的值,Zookeeper 啟動時會讀取這個文件,拿到里面的數據與 zoo.cfg 里面的配置信息比較從而判斷到底是那個 server。
因為這三個節點是在同一臺機器上,所以分別配置了不同的端口,不同的數據目錄。
3.2 啟動集群
集群模式的zk,每個節點一次啟動。
#start sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh start conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Starting zookeeper ... STARTED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh start conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Starting zookeeper ... STARTED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh start conf/zk3.cfg JMX enabled by default Using config: conf/zk3.cfg Starting zookeeper ... STARTED #status sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Mode: follower sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Mode: leader sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk3.cfg JMX enabled by default Using config: conf/zk3.cfg Mode: follower |
從3個節點的狀態可以看出,zk2是leader, zk1和zk3是follower.
3.3 連接zk集群
我啟動了三個terminal來分別連接集群
sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkCli.sh -server localhost:2181 Connecting to localhost:2181 ... Welcome to ZooKeeper! ... [zk: localhost:2181(CONNECTED) 0] sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkCli.sh -server localhost:2182 Connecting to localhost:2182 ... Welcome to ZooKeeper! ... [zk: localhost:2182(CONNECTED) 0] sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkCli.sh -server localhost:2183 Connecting to localhost:2183 ... Welcome to ZooKeeper! ... [zk: localhost:2183(CONNECTED) 0] |
3.4 測試zk集群
#在zk1上新建節點 [zk: localhost:2181(CONNECTED) 0] ls / [zookeeper] [zk: localhost:2181(CONNECTED) 7] create /node node_in_root Created /node [zk: localhost:2181(CONNECTED) 9] ls / [node, zookeeper] [zk: localhost:2181(CONNECTED) 10] get /node node_in_root cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000a mtime = Wed Sep 30 16:24:00 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 12 numChildren = 0 #zk2上來看看,我們在zk1上新建的節點從zk2上也能看到 [zk: localhost:2182(CONNECTED) 1] ls / [node, zookeeper] [zk: localhost:2182(CONNECTED) 2] get /node node_in_root cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000a mtime = Wed Sep 30 16:24:00 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 12 numChildren = 0 #zk3上來看看,然后修改下試試 [zk: localhost:2183(CONNECTED) 0] get /node node_in_root cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000a mtime = Wed Sep 30 16:24:00 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 12 numChildren = 0 [zk: localhost:2183(CONNECTED) 1] set /node node_in_root_modified_by_3 cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000b mtime = Wed Sep 30 16:25:24 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 26 numChildren = 0 [zk: localhost:2183(CONNECTED) 2] get /node node_in_root_modified_by_3 cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000b mtime = Wed Sep 30 16:25:24 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 26 numChildren = 0 #zk2上看到,節點的值變了哎 [zk: localhost:2182(CONNECTED) 3] get /node node_in_root_modified_by_3 cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000b mtime = Wed Sep 30 16:25:24 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 26 numChildren = 0 #zk1上看到的值也變了哎 [zk: localhost:2181(CONNECTED) 11] get /node node_in_root_modified_by_3 cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000b mtime = Wed Sep 30 16:25:24 CST 2015 pZxid = 0x20000000a cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 26 numChildren = 0 #在zk2上建個子節點 [zk: localhost:2182(CONNECTED) 4] create /node/childnode secend_node_create_in_2 Created /node/childnode #zk1上看到子節點了 [zk: localhost:2181(CONNECTED) 12] get /node node_in_root_modified_by_3 cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000b mtime = Wed Sep 30 16:25:24 CST 2015 pZxid = 0x20000000c cversion = 1 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 26 numChildren = 1 [zk: localhost:2181(CONNECTED) 13] ls /node [childnode] [zk: localhost:2181(CONNECTED) 14] ls /node/childnode [] [zk: localhost:2181(CONNECTED) 15] get /node/childnode secend_node_create_in_2 cZxid = 0x20000000c ctime = Wed Sep 30 16:26:41 CST 2015 mZxid = 0x20000000c mtime = Wed Sep 30 16:26:41 CST 2015 pZxid = 0x20000000c cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 0 #zk3上也看到子節點了呢 [zk: localhost:2183(CONNECTED) 3] get /node/childnode secend_node_create_in_2 cZxid = 0x20000000c ctime = Wed Sep 30 16:26:41 CST 2015 mZxid = 0x20000000c mtime = Wed Sep 30 16:26:41 CST 2015 pZxid = 0x20000000c cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 23 numChildren = 0 #zk3上刪除子節點 [zk: localhost:2183(CONNECTED) 4] ls / [node, zookeeper] [zk: localhost:2183(CONNECTED) 5] delete /node Node not empty: /node [zk: localhost:2183(CONNECTED) 6] delete /node/childnode [zk: localhost:2183(CONNECTED) 7] ls /node [] [zk: localhost:2183(CONNECTED) 8] ls / [node, zookeeper] #zk2上看到子節點也被刪除了,再刪除/node [zk: localhost:2182(CONNECTED) 5] ls / [node, zookeeper] [zk: localhost:2182(CONNECTED) 6] get /node node_in_root_modified_by_3 cZxid = 0x20000000a ctime = Wed Sep 30 16:24:00 CST 2015 mZxid = 0x20000000b mtime = Wed Sep 30 16:25:24 CST 2015 pZxid = 0x20000000e cversion = 2 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 26 numChildren = 0 [zk: localhost:2182(CONNECTED) 7] delete /node [zk: localhost:2182(CONNECTED) 8] ls / [zookeeper] #zk1上看到節點也咩有了 [zk: localhost:2181(CONNECTED) 16] ls / [zookeeper] |
3.4 stop zk集群
我們來看看如何stop集群
#先看一眼各個zk節點的狀態 sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Mode: follower sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Mode: leader sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk3.cfg JMX enabled by default Using config: conf/zk3.cfg Mode: follower #開始stop節點了啊,先從哪個下手呢?恩,先從leader下手。可以看到,zk2 stopped以后,zk3成了leader,zk1還是follower sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh stop conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Stopping zookeeper ... STOPPED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Mode: follower sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk3.cfg JMX enabled by default Using config: conf/zk3.cfg Mode: leader #還有兩個節點,我們這次從zk1下手,zk1 stopped以后,zk3也罷工了。。。 sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh stop conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Stopping zookeeper ... STOPPED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk3.cfg JMX enabled by default Using config: conf/zk3.cfg Error contacting service. It is probably not running. sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh stop conf/zk3.cfg JMX enabled by default Using config: conf/zk3.cfg Stopping zookeeper ... STOPPED #兩個節點時,一個leader,一個follower,follower stopped以后,leader罷工了,那么如果先停leader呢? sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh start conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Starting zookeeper ... STARTED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Error contacting service. It is probably not running. sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh start conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Starting zookeeper ... STARTED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Mode: leader sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Mode: follower sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh stop conf/zk1.cfg JMX enabled by default Using config: conf/zk1.cfg Stopping zookeeper ... STOPPED sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh status conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Error contacting service. It is probably not running. sunguoli@sunguolideMacBook-Pro:~/zookeeper/zookeeper-3.4.6$ bin/zkServer.sh stop conf/zk2.cfg JMX enabled by default Using config: conf/zk2.cfg Stopping zookeeper ... STOPPED |
4. 用zookeeper進行配置管理
4.1 監聽節點
import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import java.io.IOException; /** * Desc:監控各個node的情況,然后寫到數據庫里面 * Author:sunguoli * Date:15/9/25 * Time:上午11:35 */ public class zkWatcherClient implements Runnable, Watcher{ private static final Logger logger = LoggerFactory.getLogger(zkWatcherClient.class); private String zkServer; private String watchedNode; //private int zkTimeout; private int zkWatchInterval; private ZooKeeper zooKeeper; public zkWatcherClient(String zkServer, String watchedNode, int zkTimeout, int interval) throws IOException{ this.zkServer = zkServer; this.watchedNode = watchedNode; //this.zkTimeout = zkTimeout; this.zkWatchInterval = interval; this.zooKeeper = new ZooKeeper(zkServer, zkTimeout, this); } public void init() { try { Thread thread = new Thread(this); thread.start(); } catch (Exception e) { logger.error("init zkWatcherClient, error:", e); } } @Override public void process(WatchedEvent event) { try{ String nodeValue = new String(zooKeeper.getData(watchedNode, true, null)); logger.info("========the latest zookeeper node status is:" + nodeValue); updateNodeStatusToDB(nodeValue); } catch (KeeperException e){ e.printStackTrace(); } catch (InterruptedException e){ e.printStackTrace(); } } @Override public void run() { while(true){ try{ logger.info("========web-zookeeper-watcher is watching..."); zooKeeper.exists(watchedNode, this); }catch(KeeperException e){ e.printStackTrace(); }catch(InterruptedException e){ e.printStackTrace(); } try{ logger.info("========web-zookeeper-watcher sleep..."); Thread.sleep(zkWatchInterval); }catch(InterruptedException e){ e.printStackTrace(); } } } /** * 檢查狀態并寫DB * @param nodeValue */ public void updateNodeStatusToDB(String nodeValue){ //TODO 檢查是否在數據庫中已經存在,不要寫重復數據,如果不存在就寫數據庫 //TODO node節點的格式是什么樣的? logger.info("========writing DB:" + nodeValue); } public static void main(String[] args) throws Exception{ zkWatcherClient client = new zkWatcherClient("localhost:2181", "/node", 2000, 2000); Thread thread = new Thread(client); thread.start(); } } |
可以在配置信息寫到配置文件中:
#zookeeper properties zookeeper_server=localhost:2181 #需要監控的節點 zookeeper_watch_node=/node zookeeper_time_out=2000 zookeeper_watch_interval=2000 |
并且設置成隨項目啟動:
<bean id="zkWatcherClient" class="com.*.*.*.zkWatcherClient" init-method="init"> <constructor-arg index="0" value="${zookeeper_server}"/> <constructor-arg index="1" value="${zookeeper_watch_node}"/> <constructor-arg index="2" value="${zookeeper_time_out}"/> <constructor-arg index="3" value="${zookeeper_watch_interval}"/> </bean> |
4.2 更改節點
import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; /** * Desc: * Author:sunguoli * Date:15/10/6 * Time:下午2:04 */ public class zkConfigServer implements Watcher{ ZooKeeper zkServer = null; String zkNode; zkConfigServer(String zkServer, String zkNode, int zkTimeout) { this.zkNode = zkNode; try { this.zkServer = new ZooKeeper(zkServer, zkTimeout, this); Stat st = this.zkServer.exists(zkNode, true); if (st == null) { this.zkServer.create(zkNode, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (IOException e) { e.printStackTrace(); this.zkServer = null; } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } void updateConfig(String str) { try { Stat s = this.zkServer.exists(this.zkNode, true); this.zkServer.setData(this.zkNode, str.getBytes(), s.getVersion()); } catch (Exception e) { e.printStackTrace(); } } @Override public void process(WatchedEvent event) { System.out.println(event.toString()); try { this.zkServer.exists(zkNode, true); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { zkConfigServer configServer = new zkConfigServer("localhost:2181", "/node", 2000); configServer.updateConfig("haha"); } } |
可以運行代碼可以看到/node節點的值被設置成了"haha",或者當節點不存在時,會首先創建節點。
參考文章:
官方文檔,very good! http://zookeeper.apache.org/doc/r3.4.6/zookeeperStarted.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/
http://qindongliang.iteye.com/blog/1985087
http://blog.javachen.com/2013/08/23/publish-proerties-using-zookeeper.html
http://blog.csdn.net/copy202/article/details/8957939
http://blog.fens.me/hadoop-zookeeper-intro/
http://shiyanjun.cn/archives/474.html