前言
我們都知道zookeeper可以作爲dubbo的註冊中心,但是那只是zookeeper功能的冰山一角。
zookeeper的定位是一個分佈式應用程序的分佈式協調服務。它提供了一組簡單的原語,分佈式應用程序可以基於這些原語來實現用於同步(synchronization),配置維護(configuration maintenance)以及組(groups)和命名(naming)的更高級別的服務。
本篇主要從理論和實踐兩方面講解zookeeper從單機到集羣的相關知識
介紹
zookeeper有三大特點
- 高性能
- 高可用性
- 嚴格有序訪問
zookeeper高性能意味着它可以在大型的分佈式系統中使用。高可用性使它不會成爲單點故障。嚴格有序訪問意味着可以在客戶端上實現複雜的同步原語。
zookeeper提供了類似文件系統的目錄樹結構樣式的數據模型。與文件系統不同的是,zookeeper每一個節點都可以存儲數據。最大爲1M,數據是二進制安全的,存儲數據少是爲了保證zookeeper的高性能。zookeeper的數據模型如下
(圖片來源:http://zookeeper.apache.org/doc/current/zookeeperOver.html)
zookeeper的節點也叫Znode,分爲臨時節點和持久節點
- 臨時節點:生命週期和
session
相同,session
消失時,zookeeper會自動刪除臨時節點 - 持久節點:持久化到硬盤,不會隨着
session
的消失而被刪除
瞭解了關於zookeeper的基礎知識後,就可以開始安裝zookeeper了。
實驗環境
- VMware Workstation 15
- CentOS Linux release 7.7.1908
- zookeeper-3.4.14
- jdk-8u144-linux-x64.tar.gz
注意事項
- 文章後面集羣部分四個節點ip分別爲
192.168.1.101
、192.168.1.102
、192.168.1.103
、192.168.1.104
- 確保四個節點都安裝了
JDK1.8
,並且配置好了環境變量 - 確保四個節點能夠相互通信
- 確保Linux的
wget
、tar
等基礎命令可用 - 建議先關閉防火牆,Centos 7操作如下
firewall-cmd --state ## 查看防火牆狀態 not running表示已經關閉 systemctl stop firewalld.service ## 關閉防火牆 systemctl disable firewalld.service ## 禁止開機啓動防火牆
單機安裝
-
下載
wget https://downloads.apache.org/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
-
解壓
tar -zxvf zookeeper-3.4.14.tar.gz
-
配置環境變量
vim /etc/profile
配置內容如下
export ZOOKEEPER_HOME=/root/zookeeper-3.4.14 export PATH=$PATH:$ZOOKEEPER_HOME/bin
刷新配置文件
source /etc/profile
-
修改zookeeper配置文件
cd zookeeper-3.4.14/conf/ ## zookeeper默認加載的是zoo.cfg文件 cp zoo_sample.cfg zoo.cfg ## 創建zookeeper數據存放目錄 mkdir /var/lib/zookeeper ## 編輯配置 vim zoo.cfg
zookeeper的配置文件參數比較少,基本上需要修改只有數據目錄
# the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=/var/lib/zookeeper
-
啓動
## start 表示後臺啓動 zkServer.sh start ## start-foreground表示在前臺啓動 zkServer.sh start-foreground ## 其它啓動參數參考命令 zkServer.sh help
-
查看狀態
zkServer.sh status
可以查看zookeeper服務的狀態[root@localhost ~]# zkServer.sh status ZooKeeper JMX enabled by default Using config: /root/zookeeper-3.4.14/bin/../conf/zoo.cfg Mode: standalone
單節點的安裝,很容易就完成了。
操作
zkCli.sh
客戶端可以連接zookeeper服務,直接運行即可,默認連接本機的zookeeper服務。連接成功後輸入help
可以看到zookeeper服務提供的功能。
[zk: localhost:2181(CONNECTED) 0] help
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
history
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit
getAcl path
close
connect host:port
命令比較簡單,常用的就是create
、rmr
、set
、ls
,也就是增刪改查。
需要注意的是create [-s] [-e] path data acl
命令中data
參數是必須的,如果不想寫入數據,可以寫空字符串""
。
[zk: localhost:2181(CONNECTED) 2] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 3] create sicimike
[zk: localhost:2181(CONNECTED) 4] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 5] create sicimike ""
Command failed: java.lang.IllegalArgumentException: Path must start with / character
[zk: localhost:2181(CONNECTED) 6] create /sicimike ""
Created /sicimike
[zk: localhost:2181(CONNECTED) 7] ls /
[sicimike, zookeeper]
上述操作可以看到兩點
- 如果不加
data
參數是無法創建Znode - 路徑必須以
/
開始
[zk: localhost:2181(CONNECTED) 8] create /sicimike/sicimike-1 "hello world"
Created /sicimike/sicimike-1
[zk: localhost:2181(CONNECTED) 9] get /sicimike/sicimike-1
hello world
cZxid = 0x3
ctime = Mon Apr 27 22:33:13 CST 2020
mZxid = 0x3
mtime = Mon Apr 27 22:33:13 CST 2020
pZxid = 0x3
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 0
從上述操作可以看出兩點
- Znode下面可以繼續創建Znode
- Znode中不僅存了命令寫入的數據
hello world
,還存放了一些元數據。其中ephemeralOwner
表示臨時節點被哪個session
持有。此處爲0x0,表示創建的是持久節點。
在用zkCli.sh
連接服務的時候,會有這樣一行日誌
2020-04-28 20:18:11,225 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server localhost/0:0:0:0:0:0:0:1:2181, sessionid = 0x100005d814d0002, negotiated timeout = 30000
從日誌中可以看到,本次連接的sessionid
爲0x100005d814d0002
。連接之後,創建一個臨時節點
[zk: localhost:2181(CONNECTED) 1] create -e /temp-sicimike "hello world"
Created /temp-sicimike
[zk: localhost:2181(CONNECTED) 2] get /temp-sicimike
hello world
cZxid = 0xe
ctime = Tue Apr 28 20:18:45 CST 2020
mZxid = 0xe
mtime = Tue Apr 28 20:18:45 CST 2020
pZxid = 0xe
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x100005d814d0002
dataLength = 11
numChildren = 0
create -e
表示創建一個臨時節點,創建後查看節點,ephemeralOwner
就變成了sessionid
。
客戶端退出(命令quit
)時,session
斷開,臨時節點也會被刪除。
單節點的安裝和操作都非常簡單,接下來進行集羣的搭建。
集羣搭建
zookeeper的集羣是一主多從模式。
(圖片來源:http://zookeeper.apache.org/doc/current/zookeeperOver.html)
集羣結構如圖所示,集羣中的節點分爲三種角色:leader
、follower
和observer
。
leader
就是主節點,最多隻有一個follower
是從節點,可以有多個observer
也是從節點,可以有多個,和follower
唯一的區別就是不參與選舉過程。也就是當leader
故障之後,follower
重新選出一個leader
,observer
不會參與這個過程
客戶端可以連接任意節點,但是會把寫操作交給leader去執行,再由leader把數據同步給所有的follower,以達到數據一致性。
zookeeper服務可以提供一組保證
- 順序一致性(Sequential Consistency):客戶端發送的所有命令都會按順序執行。
- 原子性(Atomicity ):更新成功或失敗。沒有部分成功。
- 單個系統映像(Single System Image):無論客戶端連接到哪個節點,客戶端都將看到相同的服務視圖。
- 可靠性(Reliability ):應用更新後,此更新會被持久化。
- 及時性(Timeliness ):確保系統的客戶視圖在特定時間範圍內是最新的。
瞭解了zookeeper集羣相關的知識後,就可以開始搭建zookeeper集羣了。
集羣的搭建起來非常簡單,只需要稍微改寫配置文件即可。
修改zoo.cfg
文件,四個節點配置內容相同,均配置如下信息
# The number of milliseconds of each tick
## 每次心跳的超時時間,單位毫秒
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
## leader和follower之間初始連接的最大心跳次數 initLimit * tickTime 就是最大時間
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
## leader和follower之間通信的最多能忍受多少次心跳,超時的follower會被踢除集羣 syncLimit * tickTime 就是最大時間
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/var/lib/zookeeper
## zookeeper的集羣需要手動規劃
server.1=192.168.1.101:2888:3888
server.2=192.168.1.102:2888:3888
server.3=192.168.1.103:2888:3888
server.4=192.168.1.104:2888:3888
每個ip配置了2個端口,其中3888
端口的作用是:集羣第一次啓動尚未選出leader
,或者leader
已經出故障時,節點之間通過3888
端口通信,選出leader
。選出leader
後,其餘的節點都會連接leader
的2888
端口進行通信。
記得在每個節點上創建/var/lib/zookeeper
目錄,並且在該目錄下新建文件,寫入自己的服務id
## 節點1 192.168.1.101
echo 1 > myid
## 節點2 192.168.1.103
echo 2 > myid
## 節點3 192.168.1.103
echo 3 > myid
## 節點4 192.168.1.104
echo 4 > myid
配置好了之後,依次啓動四個節點。
此時再看節點狀態
[root@localhost ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /root/zookeeper-3.4.14/bin/../conf/zoo.cfg
Mode: leader
[root@localhost ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /root/zookeeper-3.4.14/bin/../conf/zoo.cfg
Mode: follower
有一個已經變成了leader
,其餘的成了follower
。
客戶端連接任意一個節點,都是都是可以寫入的。其實是follower
節點會把寫入命令發送給leader去執行,follower
節點只負責讀操作。
之前在查看節點數據的時候,Znode節點中存儲數據的格式如下
[zk: localhost:2181(CONNECTED) 6] get /sicimike
hello
cZxid = 0x100000007
ctime = Tue Apr 28 22:03:51 CST 2020
mZxid = 0x100000007
mtime = Tue Apr 28 22:03:51 CST 2020
pZxid = 0x100000007
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
除了客戶端存入的數據hello
,之外,還存儲了一些元數據。
cZxid
:是一個64位的二進制數,高32位表示leader的紀元,低32位表示創建的事務ID。因爲zookeeper是可以保證順序一致性的,所以的寫操作都由leader來完成,所以寫操作會有一個遞增的事務IDctime
:Znode創建時間mZxid
:和cZxid
一樣,高32位表示leader的紀元,低32位表示修改的事務IDmtime
:Znode修改時間pZxid
:高32位表示leader的紀元,低32位表示當前Znode下,創建最後一個子Znode的事務ID
在創建Znode的時候,可以看到create
命令的結構如下
create [-s] [-e] path data acl
除了可加參數-e
表示臨時節點,還可以加參數-s
,操作命令如下
[zk: localhost:2181(CONNECTED) 7] create -s /sicimike "hello world"
Created /sicimike0000000001
[zk: localhost:2181(CONNECTED) 8] create -s /sicimike "hello world 1"
Created /sicimike0000000002
[zk: localhost:2181(CONNECTED) 9] create -s /sicimike "hello world 3"
Created /sicimike0000000003
[zk: localhost:2181(CONNECTED) 10] ls /
[sicimike0000000002, sicimike0000000003, sicimike, zookeeper, sicimike0000000001]
[zk: localhost:2181(CONNECTED) 11] get /sicimike0000000001
hello world
cZxid = 0x100000008
ctime = Tue Apr 28 22:20:27 CST 2020
mZxid = 0x100000008
mtime = Tue Apr 28 22:20:27 CST 2020
pZxid = 0x100000008
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 0
可以看到了-s
參數後,創建的Znode都帶有數字,並且是一個遞增的數字。實際上-s
是sequence
(序列)的意思,就是創建帶有序列的Znode。
帶序列的Znode可以用於以下場景,當多個客戶端想要寫入同一個路徑(path),而又不想相互覆蓋的時候就可以使用帶序列的節點。上面的操作中path
都是一樣的,但是Znode沒有相互覆蓋。
前文中說Znode可以分爲臨時節點和持久節點,這裏又可以分成非序列節點和序列節點。根據排列組合可以知道,zookeeper的Znode可以分成四種:臨時序列節點、臨時非序列節點、持久非序列節點和持久序列節點。
連接狀態
前文配置可以看到,集羣節點之間通過2888和3888兩個端口通信,因此可以通過查看這兩個端口的連接狀態開觀察集羣的連接情況。操作命令如下
netstat -natp |egrep ('2888'|'3888')
四個集羣節點的連接信息分別如下(博主搭建的集羣中,192.168.1.104
是leader)
- 192.168.101
tcp6 0 0 192.168.1.101:3888 :::* LISTEN 1253/java tcp6 0 0 192.168.1.101:3888 192.168.1.102:43972 ESTABLISHED 1253/java tcp6 0 0 192.168.1.101:3888 192.168.1.104:45424 ESTABLISHED 1253/java tcp6 0 0 192.168.1.101:3888 192.168.1.103:56906 ESTABLISHED 1253/java tcp6 0 0 192.168.1.101:59638 192.168.1.104:2888 ESTABLISHED 1253/java
- 192.168.102
tcp6 0 0 192.168.1.102:3888 :::* LISTEN 1179/java tcp6 0 0 192.168.1.102:43972 192.168.1.101:3888 ESTABLISHED 1179/java tcp6 0 0 192.168.1.102:57100 192.168.1.104:2888 ESTABLISHED 1179/java tcp6 0 0 192.168.1.102:3888 192.168.1.103:56180 ESTABLISHED 1179/java tcp6 0 0 192.168.1.102:3888 192.168.1.104:35646 ESTABLISHED 1179/java
- 192.168.103
tcp6 0 0 192.168.1.103:3888 :::* LISTEN 1328/java tcp6 0 0 192.168.1.103:36788 192.168.1.104:2888 ESTABLISHED 1328/java tcp6 0 0 192.168.1.103:56180 192.168.1.102:3888 ESTABLISHED 1328/java tcp6 0 0 192.168.1.103:3888 192.168.1.104:37652 ESTABLISHED 1328/java tcp6 0 0 192.168.1.103:56906 192.168.1.101:3888 ESTABLISHED 1328/java
- 192.168.104
tcp6 0 0 192.168.1.104:2888 :::* LISTEN 1184/java tcp6 0 0 192.168.1.104:3888 :::* LISTEN 1184/java tcp6 0 0 192.168.1.104:2888 192.168.1.103:36788 ESTABLISHED 1184/java tcp6 0 0 192.168.1.104:35646 192.168.1.102:3888 ESTABLISHED 1184/java tcp6 0 0 192.168.1.104:45424 192.168.1.101:3888 ESTABLISHED 1184/java tcp6 0 0 192.168.1.104:37652 192.168.1.103:3888 ESTABLISHED 1184/java tcp6 0 0 192.168.1.104:2888 192.168.1.101:59638 ESTABLISHED 1184/java tcp6 0 0 192.168.1.104:2888 192.168.1.102:57100 ESTABLISHED 1184/java
根據信息可以知道四個節點連接情況如下
綠色的線表示3888端口,也就是用來選出leader的端口,而紅色的線是2888端口,是用來leader和follower之間同步指令的端口。
有了連接信息,就能更好的瞭解zookeeper集羣的工作原理。
總結
zookeeper單機和集羣的搭建都比較簡單,重要的是理解zookeeper的內部結構。對zookeeper有個整體的認識,這將有助於我們更好的使用zookeeper。
參考
- http://zookeeper.apache.org/doc/current/zookeeperOver.html