本文主要學習ZooKeeper的體系結構、節點類型、節點監聽、常用命令等基礎知識,最後還學習了ZooKeeper的高可用集群的搭建與測試。目錄如下:
ZooKeeper簡介與安裝
ZooKeeper體系結構
ZooKeeper節點類型
ZooKeeper節點監聽
ZooKeeper常用命令
ZooKeeper客戶端開發
ZooKeeper高可用集群搭建與測試
希望能給想快速掌握ZooKeeper的同學有所幫助。
ZooKeeper是一個分布式協調服務框架,通常用於解決分布式項目中遇到的一些管理協調的問題,如統一命名服務、分布式配置管理,分布式鎖、集群節點狀態協調等等。
到http://apache.fayea.com/zookeeper/下載zookeeper-3.4.9,ftp上傳至linux
[root@localhost ftpuser]# tar -zxvf zookeeper-3.4.9.tar.gz
在zookeeper的解壓目錄下創建以下兩個文件夾
[root@localhost zookeeper-3.4.9]# mkdir data
[root@localhost zookeeper-3.4.9]# mkdir logs
到zookeeper的解壓目錄的conf目錄下,將zoo_sample.cfg 文件拷貝一份,命名為為 zoo.cfg
[root@localhost conf]# cp zoo_sample.cfg zoo.cfg
[root@localhost conf]# vi zoo.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
# 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=/home/ftpuser/zookeeper-3.4.9/data
dataLogDir=/home/ftpuser/zookeeper-3.4.9/logs
# the port at which the clients will connect
clientPort=2181
server.1=192.168.2.129:2888:3888
# 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
在zoo.cfg 配置dataDir,dataLogDir,server。
server.A=B:C:D:其中 A 是一個數字,表示這個是第幾號服務器;B 是這個服務
器的 IP 地址;C 表示的是這個服務器與集群中的 Leader 服務器交換信息的端口;D 表示的是萬一集群中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader,而這個端口就是用來執行選舉時服務器相互通信的端口。
clientPort 這個端口就是客戶端(應用程序)連接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端口接受客戶端的訪問請求
進入/home/ftpuser/zookeeper-3.4.9/data並創建myid文件
[root@localhost conf]# cd /home/ftpuser/zookeeper-3.4.9/data
[root@localhost data]# vi myid
1
編輯 myid 文件,並在對應的 IP 的機器上輸入對應的編號。如在 zookeeper 上,myid
文件內容就是 1。如果只在單點上進行安裝配置,那麼只有一個 server.1
[root@localhost data]# vi /etc/profile
在文件末尾添加zookeeper 環境變量
# zookeeper env
export ZOOKEEPER_HOME=/home/ftpuser/zookeeper-3.4.9/
export PATH=$ZOOKEEPER_HOME/bin:$PATH
執行source /etc/profile命令是環境變量生效,執行echo $ZOOKEEPER_HOME查看
在防火牆中打開要用到的端口 2181、2888、3888。打開/etc/sysconfig/iptables增加以下 3 行
[root@localhost data]# cat /etc/sysconfig/iptables
# Generated by iptables-save v1.4.7 on Thu Jun 2 22:41:13 2016
*filter
:INPUT ACCEPT [5:320]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4:464]
-A INPUT -p udp -m udp --dport 23 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 23 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2181 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2888 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3888 -j ACCEPT
COMMIT
# Completed on Thu Jun 2 22:41:13 2016
[root@localhost data]#
查看端口啟動狀態
[root@localhost data]# service iptables status
[root@localhost zookeeper-3.4.9]# cd bin
[root@localhost bin]# ll
total 36
-rwxr-xr-x. 1 1001 1001 232 Aug 23 15:39 README.txt
-rwxr-xr-x. 1 1001 1001 1937 Aug 23 15:39 zkCleanup.sh
-rwxr-xr-x. 1 1001 1001 1032 Aug 23 15:39 zkCli.cmd
-rwxr-xr-x. 1 1001 1001 1534 Aug 23 15:39 zkCli.sh
-rwxr-xr-x. 1 1001 1001 1579 Aug 23 15:39 zkEnv.cmd
-rwxr-xr-x. 1 1001 1001 2696 Aug 23 15:39 zkEnv.sh
-rwxr-xr-x. 1 1001 1001 1065 Aug 23 15:39 zkServer.cmd
-rwxr-xr-x. 1 1001 1001 6773 Aug 23 15:39 zkServer.sh
[root@localhost bin]# zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/ftpuser/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@localhost ~]# tail -f /home/ftpuser/zookeeper-3.4.9/bin/zookeeper.out
[root@localhost bin]# jps
2011 QuorumPeerMain
1245 Bootstrap
2030 Jps
[root@localhost bin]#
其中,QuorumPeerMain 是 zookeeper 進程,啟動正常。查看狀態
[root@localhost bin]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /home/ftpuser/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: standalone
[root@localhost bin]#
[root@localhost bin]# zkServer.sh stop
ZooKeeper JMX enabled by default
Using config: /home/ftpuser/zookeeper-3.4.9/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
[root@localhost bin]#
[root@localhost bin]# ./zkCli.sh -server 192.168.2.129:2181
輸入help命令來查看有哪些命令
[zk: 192.168.2.129:2181(CONNECTED) 0] 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
[zk: 192.168.2.129:2181(CONNECTED) 1]
ZooKeeper的體系結構由一個命名空間組成,類似於一個標准的文件系統。命名空間由稱為znodes節點的數據寄存器組成,用ZooKeeper說法,這是類似於文件和目錄。不同於典型的文件系統的是,這是專為存儲數據而設計的,也就是說,ZooKeeper的節點數據是存儲在內存中中,可以實現高吞吐量和低延遲的數據。
ZooKeeper允許分布式進程(注意,這裡強調的是進程,分布式環境中往往是不同主機之間的協調訪問)通過共享ZooKeeper命名空間組織的znodes節點數據來實現相互協調。
ZooKeeper命名空間結構示意圖
從上面的ZooKeeper命名空間結構示意圖中可以看出,ZooKeeper命名空間的根路徑是“/”,每一個znode都有自己的path,每一個znode都存儲著一份協調數據,這些數據包括狀態信息、配置、位置信息等等。由於znode維護的數據主要是用於分布式進程之間協調的,因此這些數據通常非常小。
命名空間中的每個znode中的數據都是可讀可寫的,但每個節點都有一個訪問控制列表(ACL),限制誰可以做什麼。
命名空間中的每個znode都有自己的類型,znode的類型主要有下面幾種:
(1)PERSISTEN,永久的(只要客戶端不刪除,則永遠存在)
(2)PERSISTENT_SEQUENTIAL,永久且有序號的
(3)EMPEMERAL ,短暫的(只要客戶端斷開連接,則會被自動刪除)
(4)EMPEMERAL_SEQUENTIAL, 短暫且有序號的
客戶端可以對znode進行監聽,當znode節點發生變化時,監聽事件將會被觸發,客戶端接收到一個數據包說znode節點已發生改變。
ZooKeeper的常用命令非常簡單,也就幾條,下面我們來學習ZooKeeper的常用命令,加深對ZooKeeper體系結構的理解。
(1)ls——查看節點(路徑)
[zk: 192.168.2.129:2181(CONNECTED) 1] ls /
[dubbo, zookeeper]
[zk: 192.168.2.129:2181(CONNECTED) 2] ls /zookeeper
[quota]
[zk: 192.168.2.129:2181(CONNECTED) 3] ls /dubbo
[com.alibaba.dubbo.monitor.MonitorService, com.mcweb.api.service.IUserService]
[zk: 192.168.2.129:2181(CONNECTED) 4]
(2)create——在命名空間中的某個路徑下創建節點
[zk: 192.168.2.129:2181(CONNECTED) 7] create /nodes1 "test node"
Created /nodes1
[zk: 192.168.2.129:2181(CONNECTED) 8] ls /
[dubbo, nodes1, zookeeper]
(3)get——獲取命名空間中某個節點的數據
[zk: 192.168.2.129:2181(CONNECTED) 9] get /nodes1
test node
cZxid = 0x227
ctime = Tue Sep 27 04:01:49 CST 2016
mZxid = 0x227
mtime = Tue Sep 27 04:01:49 CST 2016
pZxid = 0x227
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 0
[zk: 192.168.2.129:2181(CONNECTED) 10]
(4)set——設置命名空間中某個節點的數據
[zk: 192.168.2.129:2181(CONNECTED) 10] set /nodes1 "nodes1 data have been setted"
cZxid = 0x227
ctime = Tue Sep 27 04:01:49 CST 2016
mZxid = 0x228
mtime = Tue Sep 27 04:07:04 CST 2016
pZxid = 0x227
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 28
numChildren = 0
[zk: 192.168.2.129:2181(CONNECTED) 11] get /nodes1
nodes1 data have been setted
cZxid = 0x227
ctime = Tue Sep 27 04:01:49 CST 2016
mZxid = 0x228
mtime = Tue Sep 27 04:07:04 CST 2016
pZxid = 0x227
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 28
numChildren = 0
[zk: 192.168.2.129:2181(CONNECTED) 12]
(5)delete——刪除命名空間中某個節點
[zk: 192.168.2.129:2181(CONNECTED) 13] delete /nodes1
[zk: 192.168.2.129:2181(CONNECTED) 14] get /nodes1
Node does not exist: /nodes1
[zk: 192.168.2.129:2181(CONNECTED) 15]
刪除節點信息還可以用rmr命令。好了,常用命令介紹完了,其他命令可以通過help命令來查看。下面我們來學習ZooKeeper Java客戶端的使用。
public class ZKTest { private ZooKeeper zk = null; @Before public void init() throws Exception { zk = new ZooKeeper("192.168.2.129:2181", 2000, new Watcher() { /** * 監聽事件發生時的回調方法 */ @Override public void process(WatchedEvent event) { if (event.getType() == EventType.None) { System.out.println("Event:null"); return; } System.out.println("EventType:" + event.getType()); System.out.println("Path" + event.getPath()); try { zk.getData("/nodes1", true, null); zk.getChildren("/nodes1", true); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } } }); } /** * 向zookeeper服務(集群)中注冊數據,添加znode * @throws Exception */ @Test public void testCreateZnode() throws Exception { zk.create("/nodes1", "nodes1".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 在一個父節點的范圍之內,sequential的順序是遞增的 zk.create("/nodes1/testNode1", "/nodes1/testNode1".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); zk.create("/nodes1/testNode2", "/nodes1/testNode2".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); // 換一個父節點,序號的遞增順序重新開始 zk.create("/nodes2", "nodes2".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.create("/nodes2/testNode1", "/nodes2/testNode1".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); zk.create("/nodes3", "/nodes3".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.close(); } /** * 從zookeeper中刪除znode * @throws Exception */ @Test public void testDeleteZnode() throws Exception { // 參數1:要刪除的節點的路徑 參數2:要刪除的節點的版本,-1匹配所有版本 // 只能刪除不為空的節點 zk.delete("/nodes3", -1); Stat exists = zk.exists("/nodes3", false); System.out.println(exists); } @Test public void testUpdateZnode() throws Exception { byte[] data = zk.getData("/nodes1", false, null); System.out.println(new String(data, "utf-8")); zk.setData("/nodes1", "/nodes1 data changed".getBytes("utf-8"), -1); data = zk.getData("/nodes1", false, null); System.out.println(new String(data, "utf-8")); } /** * 獲取子節點信息 * @throws Exception */ @Test public void testGetChildren() throws Exception { List<String> children = zk.getChildren("/nodes1", false); for (String child : children) { System.out.println(child); } } /** * zk的監聽機制: * 在初始化zk對象的時候定義好回調函數,對znode進行操作時可以注冊監聽 * 監聽的znode上發生相應事件時,客戶端zk會接收到zookeeper的事件通知 * 客戶端zk根據事件調用我們事先定義好的回調函數 * @throws Exception * */ @Test public void testWatch() throws Exception { //獲取/nodes1的數據時進行監聽 //第二個參數true表示監聽 byte[] data = zk.getData("/nodes1", true, null); //獲取/nodes1的子節點時進行監聽 List<String> children = zk.getChildren("/nodes1", true); Thread.sleep(Long.MAX_VALUE); } /** * 將配置文件上傳到zookeeper中進行管理 * @throws Exception */ @Test public void testUploadConfigFileToZookeeper() throws Exception{ String schema_xml = FileUtils.readFileToString(new File("c:/web.xml")); zk.create("/conf", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.create("/conf/web.xml", schema_xml.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.close(); } }
需要注意的是,創建非葉子節點時,zk會自動加上序號,所以在操作非葉子節點時,不能像添加的時候一樣以原路徑操作,要加上序號,如:
[zk: localhost:2181(CONNECTED) 50] ls /
[dubbo, nodes2, conf, nodes1, zookeeper]
[zk: localhost:2181(CONNECTED) 51] ls /nodes1
[testNode20000000001, testNode10000000000]
[zk: localhost:2181(CONNECTED) 52] ls /nodes2
[testNode10000000000]
[zk: localhost:2181(CONNECTED) 53] get /nodes1/testNode1
Node does not exist: /nodes1/testNode1
[zk: localhost:2181(CONNECTED) 54] get /nodes1/testNode10000000000
/nodes1/testNode1
cZxid = 0x26a
.......
[zk: localhost:2181(CONNECTED) 55] delete /nodes1/testNode1
Node does not exist: /nodes1/testNode1
[zk: localhost:2181(CONNECTED) 56]
Zookeeper 集群的設計目標是高性能、高可用性、嚴格有序訪問的,其中只要有過半的節點是正常的情況下,那麼整個集群對外就是可用的。正是基於這個特性,要將 ZK 集群的節點數量要為奇數(2n+1:如 3、5、7 個節點)較為合適。
服務器 1:192.168.2.127 端口:2181、2881、3881
服務器 2:192.168.2.128 端口:2182、2882、3882
服務器 3:192.168.2.130 端口:2183、2883、3883
端口說明:
218x:客戶端(應用程序)連接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端
288x:該服務器與集群中的 Leader 服務器交換信息的端口
388x:選舉通信端口,如果集群中的 Leader 服務器掛了,需要選出一個新的 Leader,而這個端口就是用來執行選舉時服務器相互通信的端口
在操作之前,先把Xshell或者SecureCRT設置成多會話操作,即同時操縱多個會話窗口,很方便,打開127,128,130會話窗口。
Xshell:查看-》撰寫欄,Xshell的下面就會出現一個編輯框,就是撰寫欄,點擊左下角撰寫欄上的藍色圖標,然後選擇【全部會話】。
SecureCRT:右擊任何一個會話窗口下面的交互窗口,選中“發送交互到所有標簽”。
下面某些操縱就可以在撰寫欄上同時操縱三台服務器了。
將zookeeper-3.4.9.tar.gz下載或者上傳到三台服務器中的/usr/ftpuser目錄
mkdir /usr/local/zookeeper
tar -zxvf zookeeper-3.4.9.tar.gz -C /usr/local/zookeeper
cd /usr/local/zookeeper
#127
[root@localhost zookeeper]# mv zookeeper-3.4.9/ node-127
#128
[root@localhost zookeeper]# mv zookeeper-3.4.9/ node-128
#130
[root@localhost zookeeper]# mv zookeeper-3.4.9/ node-130
在各 zookeeper 節點目錄/usr/local/zookeeper/node-*下創建data和logs目錄
cd node-*
mkdir data
mkdir logs
將 zookeeper/node-*/conf 目錄下的 zoo_sample.cfg 文件拷貝一份,命名為 zoo.cfg
cd conf
cp zoo_sample.cfg zoo.cfg
#127
[root@localhost conf]# vi zoo.cfg
...
#dataDir=/tmp/zookeeper
dataDir=/usr/local/zookeeper/node-127/data
dataLogDir=/usr/local/zookeeper/node-127/logs
# the port at which the clients will connect
clientPort=2181
server.1=192.168.2.127:2881:3881
server.2=192.168.2.128:2882:3882
server.3=192.168.2.130:2883:3883
...
#128
[root@localhost conf]# vi zoo.cfg
...
#dataDir=/tmp/zookeeper
dataDir=/usr/local/zookeeper/node-128/data
dataLogDir=/usr/local/zookeeper/node-128/logs
# the port at which the clients will connect
clientPort=2182
server.1=192.168.2.127:2881:3881
server.2=192.168.2.128:2882:3882
server.3=192.168.2.130:2883:3883
...
#130
[root@localhost conf]# vi zoo.cfg
...
#dataDir=/tmp/zookeeper
dataDir=/usr/local/zookeeper/node-130/data
dataLogDir=/usr/local/zookeeper/node-130/logs
# the port at which the clients will connect
clientPort=2183
server.1=192.168.2.127:2881:3881
server.2=192.168.2.128:2882:3882
server.3=192.168.2.130:2883:3883
...
進入/usr/local/zookeeper/node-*/data,編輯 myid 文件,並在對應的 IP 的機器上輸入對應的編號。如在 node-128 上,myid 文件內容就是1,node-128 上就是 2,node-130 上就是 3。
#127
[root@localhost data]# vi /usr/local/zookeeper/node-127/data/myid ## 值為 1
#128
[root@localhost data]# vi /usr/local/zookeeper/node-128/data/myid ## 值為 2
#130
[root@localhost data]# vi /usr/local/zookeeper/node-130/data/myid ## 值為 3
在防火牆中打開要用到的端口 218x、288x、388x。打開vi /etc/sysconfig/iptables增加以下 3 行
#127
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2181 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2881 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3881 -j ACCEPT
#128
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2182 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2882 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3882 -j ACCEPT
#130
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2183 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 2883 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3883 -j ACCEPT
重啟防火牆
service iptables restart
cd node*/bin
./zkServer.sh start
./zkServer.sh status
可見,三個節點中,有一個為Mode: leader,另外兩個為Mode: follower
顯示狀態報錯參考:
http://www.cnblogs.com/xiaohua92/p/5460515.html
需要先安裝dubbo管控台,我們就用事先已經安裝好的dubbo的管控台(192.168.2.129),下面我們修改下配置文件
[root@localhost ~]#
vi /usr/local/apache-tomcat-7.0.70/webapps/ROOT/WEB-INF/dubbo.properties
dubbo.registry.address=zookeeper://192.168.2.127:2181?backup=192.168.2.128:2182,192.168.2.130:2183
dubbo.admin.root.password=dubbo129
dubbo.admin.guest.password=dubbo129
[root@localhost ~]# cd /usr/local/apache-tomcat-7.0.70/bin/
[root@localhost bin]# ./startup.sh
在《基於dubbo構建分布式項目與服務模塊》一文中,我們創建了服務消費者與服務提供者,現在我們將服務提供者注冊到zookeeper集群。
修改mcweb\mcweb-logic\src\main\resources\spring\dubbo-provider.xml
<!-- zookeeper注冊中心地址 -->
<dubbo:registry protocol="zookeeper"
address="192.168.2.127:2181,192.168.2.128:2182,192.168.2.130:2183" />
啟動mcweb-logic。在dubbo的“首頁 > 服務治理 > 服務”中可以看到已經注冊到 zookeeper 注冊中心的服務的相關情況。看看mcweb-logic的日志:
可見,應用已經連接到了zookeeper集群中的128節點。現在我們把128的zookeeper停止,看看mcweb-logic的日志變化:
可見,zookeeper集群的狀態發生了變化,當128節點停止後,應用重新連接到了zookeeper集群中的127節點。現在集群中還有兩個節點可用,集群仍可以對外可用,當再把127節點停止後,集群對外就不可用:
只要有過半的節點是正常的情況下,那麼整個zookeeper集群對外就是可用的,這是zookeeper集群高可用的基礎。