zookeeper入門與實戰

yinpeng 8年前發布 | 24K 次閱讀 分布式/云計算/大數據

來自: 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需保證高可用和強一致性;
為了支持更多的客戶端,需要增加更多Server;
Server增多,投票階段延遲增大,影響性能;
權衡伸縮性和高吞吐率,引入Observer
Observer不參與投票;
Observers接受客戶端的連接,并將寫請求轉發給leader節點;
加入更多Observer節點,提高伸縮性,同時不影響吞吐率。

客戶端(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獲取可用服務列表類

配置管理服務

分布式環境下,配置文件管理和同步是一個常見問題;
一個集群中,所有節點的配置信息是一致的,比如Hadoop;
對配置文件修改后,希望能夠快速同步到各個節點上
配置管理可交由Zookeeper實現;
可將配置信息寫入Zookeeper的一個znode上;
各個節點監聽這個znode
一旦znode中的數據被修改,zookeeper將通知各個節點


集群管理

分布式環境中,實時掌握每個節點的狀態是必要的;
可根據節點實時狀態作出一些調整;
可交由Zookeeper實現;
可將節點信息寫入Zookeeper的一個znode上;
監聽這個znode可獲取它的實時狀態變化
典型應用
Hbase中Master狀態監控與選舉

 在 Zookeeper 上創建一個 EPHEMERAL 類型的目錄節點,然后每個 Server 在它們創建目錄節點的父目錄節點上調用getChildren(String path, boolean watch) 方法并設置 watch 為 true,由于是 EPHEMERAL 目錄節點,當創建它的 Server 死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時 getChildren上的 Watch 將會被調用,所以其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是同樣的原理。
分布式通知和協調

分布式環境中,經常存在一個服務需要知道它所管理的子服務的狀態;
NameNode須知道各DataNode的狀態
JobTracker須知道各TaskTracker的狀態
心跳檢測機制可通過Zookeeper實現;
信息推送可由Zookeeper實現(發布/訂閱模式)


分布式鎖

Zookeeper是強一致的;
多個客戶端同時在Zookeeper上創建相同znode,只有一個創建成功。
實現鎖的獨占性
多個客戶端同時在Zookeeper上創建相同znode ,創建成功的那個客戶端得到鎖,其他客戶端等待。
控制鎖的時序
各個客戶端在某個znode下創建臨時znode (類型為CreateMode.EPHEMERAL_SEQUENTIAL),這樣,該znode可掌握全局訪問時序。

 實現方式是需要獲得鎖的 Server 創建一個 EPHEMERAL_SEQUENTIAL 目錄節點,然后調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點,如果正是自己創建的,那么它就獲得了這個鎖,如果不是那么它就調用 exists(String path, boolean watch) 方法并監控 Zookeeper 上目錄節點列表的變化,一直到自己創建的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。
分布式隊列

兩種隊列;
當一個隊列的成員都聚齊時,這個隊列才可用,否則一直等待所有成員到達,這種是同步隊列。
隊列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。(可通過分布式鎖實現)
同步隊列
一個job由多個task組成,只有所有任務完成后,job才運行完成。
可為job創建一個/job目錄,然后在該目錄下,為每個完成的task創建一個臨時znode,一旦臨時節點數目達到task總數,則job運行完成。

 

同步隊列實現思路:

 

創建一個父目錄 /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

 

 本文由用戶 yinpeng 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!