1 概述
1.1 簡介
ZooKeeper 是 Apache 的一個頂級項目,為分布式應用提供高效、高可用的分布式協調服務,提供了諸如數據發布/訂閱、負載均衡、命名服務、分布式協調/通知和分布式鎖等分布式基礎服務。由於 ZooKeeper 便捷的使用方式、卓越的性能(基於內存)和良好的穩定性,被廣泛地應用於諸如 Hadoop、HBase、Kafka 和 Dubbo 等大型分布式系統中。
官方地址:https://zookeeper.apache.org
1.2 角色
領導者(leader):負責進行投票的發起和決議,更新系統狀態。
跟隨者(follower):用於接收客戶端請求並給客戶端返回結果,在選主過程中進行投票。
觀察者(observer):可以接受客戶端連接,將寫請求轉發給 leader,但是observer 不參加投票的過程,只是為了擴展系統,提高讀取的速度。
1.3 節點特性
ZooKeeper 節點的生命周期取決於節點的類型。在 ZooKeeper 中,節點根據持續時間可以分為持久節點(PERSISTENT)、臨時節點(EPHEMERAL),根據是否有序可以分為順序節點(SEQUENTIAL)、和無序節點(默認是無序的)。每個客戶端連接zookeeper會產生一個session,客戶端連接關閉時session也會消失。
持久節點一旦被創建,除非主動移除,不然一直會保存在 Zookeeper 中(不會因為創建該節點的客戶端的會話失效而消失)。
1.4 數據模型
層次化的目錄結構,命名符合常規文件系統規范,類似於文件目錄。
每個節點在 Zookeeper 中叫做 Znode,並且其有一個唯一的路徑標識。
每個節點中數據存儲不能超過1M(不要把Zookeeper當數據庫用)。
1.5 特性
順序一致性。客戶端的更新將按發送順序應用。
原子性。要么成功要失敗。沒有中間狀態(最終一致性)。
單個系統映像。由於zookeeper使用復制集群,無論客戶端連接哪個節點都能看到相同的數據。
可靠性。即所有節點支持持久化。
及時性。存儲在節點中的數據會在較短時間內及時同步。
1.6 CAP
任何分布式架構都不能同時滿足C(一致性)、A(可用性)、P(分區容錯性)。zookeeper集群在保證一致性的同時,在A和P之間做了取舍,最終選擇了P,因此可用性差一點。
2 zookeeper集群搭建
2.1 網絡拓撲
2.2 環境准備
CentOS7 * 4
JDK8
Zookeeper3.6.3
2.3 安裝
在q101服務器中配置好后發送到其他服務器。q101操作如下:
wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz --no-check-certificate
tar xf apache-zookeeper-3.6.3-bin.tar.gz
mv apache-zookeeper-3.6.3-bin /usr/local/zookeepe
cd /usr/local/zookeeper/conf
mv zoo_sample.cfg zoo.cfg
vi zoo.cfg
# 1. 修改dataDir路徑為“/var/zookeeper”
# 2. 增加集群節點信息,信息如下
server.4=192.168.88.101:2888:3888
server.3=192.168.88.102:2888:3888
server.2=192.168.88.103:2888:3888
server.1=192.168.88.104:2888:3888
# 3. 保存退出
# 創建配置文件中設置的持久化目錄
mkdir /var/zookeeper
# 將節點信息中的節點ID寫如myid中
echo 4 > /var/zookeeper/myid
# 修改環境變量 增加zookeeper信息
# export ZOOKEEPER_HOME=/usr/local/zookeeper
# 在export PATH末尾追加 :$ZOOKEEPER_HOME/bin
vi /etc/profile
source /etc/profile
詳細配置說明
# leader和follower心跳時間,用於維護節點是否存活。單位毫秒
tickTime=2000
# 初始延遲。當follower追隨leader時,leader允許初始延遲時間。
# 用tickTime乘以initLimit,此處為2000*10即20秒
initLimit=10
# 數據同步時間,及超過時間數據同步失敗。
# 用tickTime乘以syncLimit,此處為2000*5即10秒
syncLimit=5
# 持久化目錄,不建議存在tmp下
dataDir=/var/zookeeper
# 客戶端連接zookeeper端口號
clientPort=2181
# 允許客戶端連接最大連接數
#maxClientCnxns=60
# dataDir中保留快照數量
#autopurge.snapRetainCount=3
# 清除任務時間間隔,單位小時。0表示禁用自動清楚功能
#autopurge.purgeInterval=1
# 自定義監控
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
# 此處為自己添加的配置。集群信息,投票過半數量=(集群節點行數 除以 2)+1
# 數字1,2,3,4為節點ID,用於謙讓出leader,投票過半后數字最大的為leader
# 端口說明:當leader掛掉后(或第一次啟動沒有leader時候)其他節點通過3888建立連接進行投票選出leader,
# 選中leader后,leader會通過2888端口來與follower進行工作
server.4=192.168.88.101:2888:3888
server.3=192.168.88.102:2888:3888
server.2=192.168.88.103:2888:3888
server.1=192.168.88.104:2888:3888
將q101相關文件傳到q102並修改myid
scp -r /usr/local/zookeeper 192.168.88.102:/usr/local/
scp -r /var/zookeeper 192.168.88.102:/var/
scp /etc/profile 192.168.88.102:/etc
# 切換到q102服務器執行,數字與配置文件中的節點ID數據一致
echo 3 > /var/zookeeper/myid
# q102執行 使環境變量生效
source /etc/profile
將q101相關文件傳到q103並修改myid
scp -r /usr/local/zookeeper 192.168.88.103:/usr/local/
scp -r /var/zookeeper 192.168.88.102:/var/
scp /etc/profile 192.168.88.103:/etc
# 切換到q103服務器執行
echo 2 > /var/zookeeper/myid
source /etc/profile
將q101相關文件傳到q104並修改myid
scp -r /usr/local/zookeeper 192.168.88.103:/usr/local/
scp -r /var/zookeeper 192.168.88.102:/var/
scp /etc/profile 192.168.88.104:/etc
# 切換到q104服務器執行
echo 1 > /var/zookeeper/myid
source /etc/profile
2.4 啟動
為了測試我們采用前台啟動,4台節點分別執行
zkServer.sh start-foreground
啟動前3台的時候會發現報錯,連接其他節點失敗,屬於正常情況,當啟動最后一台的時候發現沒有發錯了。原因是啟動前2台的時候沒有leader。當第3台啟動完成的時候,此時節點數量已經超過半數,所以開始根據zoo.cfg
配置文件中的服務ID進行選舉,因為q101服務器ID 設置的4,按節點謙讓規則q101為leader。
啟動錯誤信息如下:
節點角色驗證
在q101執行
zkServer.sh status
3 zookeeper常用功能
3.1 常用命令
啟動關閉
后台啟動:zkServer.sh start
前台啟動:zkServer.sh start-foreground
停止:zkServer.sh stop
重啟:zkServer.sh restart
查看版本:zkServer.sh version
查看狀態:zkServer.sh status
連接與退出
連接zookeeper客戶端:zkCli.sh -server 127.0.0.1:2181
連接本機可以直接輸入zkCli.sh
退出zookeeper客戶端:quit
操作命令
查看zookeeper所有命令:連接到zookeeper客戶端后輸入help
查看根目錄下包含的節點:ls /
查看節點狀態信息:ls2 /
或者使用 ls -s /
創建一個非順序的持久化節點:create [-s] [-e] path data acl
比如 create /test test-1
創建一個臨時節點:create -e /test/tmp tem-data
創建一個順序節點:create -s /test/aaa aaa-data
刪除一個節點:delete /test
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 1] create /abc
Created /abc
[zk: localhost:2181(CONNECTED) 3] ls /
[abc, zookeeper]
# 創建節點后默認數據為null
[zk: localhost:2181(CONNECTED) 9] get /abc
null
[zk: localhost:2181(CONNECTED) 4] create /abc/abcd
Created /abc/abcd
[zk: localhost:2181(CONNECTED) 15] set /abc/abcd hello
[zk: localhost:2181(CONNECTED) 16] get /abc/abcd
hello
3.2 stat結構
Zookeeper每個znode都有一個與之關聯的stat結構,類似於Unix/Linux文件系統中文件的stat結構。通過stat /xxx/xxx
查看
[zk: localhost:2181(CONNECTED) 17] stat /abc/abcd
cZxid = 0x100000003
ctime = Wed Mar 02 23:41:34 CST 2022
mZxid = 0x100000006
mtime = Wed Mar 02 23:43:32 CST 2022
pZxid = 0x100000003
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
字段含義:
cZxid:創建節點znode時的事務ID由leader維護的遞增計數器,共64位,其中前32為表示leader紀元,即leader革新換代次數,比如上面的0x1表示目前leader為做變更,如當前leader關閉后重新選舉的leader此處會變成0x2,並且后32位的事務ID重新開始;后32為表示所有的增刪改操作次數。
mZxid:修改節點znode更改的事務ID。
pZxid:添加或刪除子節點znode更改的事務ID。
ctime:表示從1970-01-01T00:00:00Z開始以毫秒為單位的znode創建時間。
mtime:表示從1970-01-01T00:00:00Z開始以毫秒為單位的znode最近修改時間。
dataVersion:表示對該znode的數據所做的更改次數。
cversion:表示對此znode的子節點進行的更改次數。
aclVersion:版本號,即znode的ACL進行更改的次數。
ephemeralOwner:如果znode是ephemeral類型節點,則這是znode所有者的 session ID。 如果znode不是ephemeral節點,則該字段設置為零。
dataLength:這是znode數據字段的長度。
numChildren:這表示znode的子節點的數量。
3.3 臨時節點
臨時節點伴隨着session會話,當會話結束后臨時節點銷毀
- 通過q101連接zookeeper客戶端查看日志
通過日志發現sessionID為:0x400000595cc0001
2. 創建臨時節點 create -e /xxxx
create -e /aaa
- 通過stat查看發現剛創建的臨時節點“bbbb”的ephemeralOwner正是連接時候的sessionID
當使用另一台服務器q102連接zkCli后查看aaa節點stat sessionID依舊是q101連接的sessionID。
3.4 持久序列
通過create -s path data
可以創建持久序列,znode被創建后,znode名稱會自動添加一個編號,編號會自動遞增。編號的遞增和節點名稱無關.
編號的遞增不會因為斷開而重置,也不會因為zookeeper重啟而重置
4 zookeeper原理
4.1 Paxos算法
它是一個基於消息傳遞的一致性算法,Leslie Lamport在1990年提出,近幾年被廣泛應用於分布式計算中,Google的Chubby,Apache的Zookeeper都是基於它的理論來實現的,Paxos還被認為是到目前為止唯一的分布式一致性算法,其它的算法都是Paxos的改進或簡化。有個問題要提一下,Paxos有一個前提:Paxos只有在一個可信的計算環境中才能成立,這個環境是不會被入侵所破壞的。
關於Paxos的具體描述可以參考:https://baike.baidu.com/item/Paxos算法
4.2 ZAB協議
ZAB(ZooKeeper Atomic broadcast)即ZooKeeper原子消息廣播協議,類似於一個二階段提交過程(2PC)屬於最終一致性。是zookeeper基於Paxos算法的簡化實現。
所有事務請求必須由一個全局唯一的服務器來協調處理,這樣的服務器被稱為 Leader服務器,而余下的其他服務器則成為 Follower服務器。 Leader服務器負責將一個客戶端事務請求轉換成一個事務 Proposal(提議),並將該 Proposal分發給集群中所有的Follower服務器。之后 Leader服務器需要等待所有 Follower服務器的反饋,一旦超過半數的 Follower服務器進行了正確的反饋后,那么 Leader就會再次向所有的 Follower服務器分發 Commit消息,要求其將前一個 Proposal進行提交。
上圖說明:
1:Client向Follower發出寫操作;
2:Follower將收到的寫請求轉發給Leader處理;
3:Leader收到請求后分配一個全局單調遞增的唯一的事務ID(即ZXID,按其先后順序來進行排序與處理);
4.1:Leader服務器會為每一個 Follower服務器都各自分配一個單獨的隊列,然后將需要廣播的事務Proposal依次放入這些隊列中去,並且根據FIFO策略進行消息發送;每一個 Follower服務器在接收到這個事務 Proposal之后,都會首先將其以事務日志的形式寫入到本地磁盤中去,並且在成功寫入后反饋給 Leader服務器個Ack響應。Leader自己也會將事務日志寫入磁盤;
4.2:當 Leader服務器接收到超過半數Follower的Ack響應后,就會廣播一個Commit消息給所有的 Follower服務器以通知其進行事務提交(寫入內存),同時 Leader自身也會完成對事務的提交。
5:由Leader將結果返回給Client1
上圖中4.1和4.2步驟,由於Leader自身也計一票,如果Follower2 由於網絡等原因沒有給Leader Ack響應,但Leader和Follower 1 兩票已超過半數,所以結果依然會成功。此時Client2請求Follower2獲取“/aaa”數據時,可以通過sync 讓Follower 2向Leader同步后再返回數據。
4.3 選舉機制
4.3.1 節點網絡
基於上面的第2章集群搭建,查看每台節點網絡連接情況
yum install net-tools
netstat -natp | egrep '(2888|3888)'
查看結果如下:
# q101信息
[root@q101 ~]# netstat -natp | egrep '(2888|3888)'
tcp6 0 0 192.168.88.101:3888 :::* LISTEN 1924/java
tcp6 0 0 192.168.88.101:58052 192.168.88.102:3888 ESTABLISHED 1924/java
tcp6 0 0 192.168.88.101:39738 192.168.88.103:3888 ESTABLISHED 1924/java
tcp6 0 0 192.168.88.101:54174 192.168.88.102:2888 ESTABLISHED 1924/java
tcp6 0 0 192.168.88.101:47726 192.168.88.104:3888 ESTABLISHED 1924/java
# q102信息
[root@q102 ~]# netstat -natp | egrep '(2888|3888)'
tcp6 0 0 192.168.88.102:2888 :::* LISTEN 1661/java
tcp6 0 0 192.168.88.102:3888 :::* LISTEN 1661/java
tcp6 0 0 192.168.88.102:3888 192.168.88.101:58052 ESTABLISHED 1661/java
tcp6 0 0 192.168.88.102:2888 192.168.88.103:38244 ESTABLISHED 1661/java
tcp6 0 0 192.168.88.102:50104 192.168.88.104:3888 ESTABLISHED 1661/java
tcp6 0 0 192.168.88.102:2888 192.168.88.104:48034 ESTABLISHED 1661/java
tcp6 0 0 192.168.88.102:2888 192.168.88.101:54174 ESTABLISHED 1661/java
tcp6 0 0 192.168.88.102:37466 192.168.88.103:3888 ESTABLISHED 1661/java
# q103信息
[root@q103 ~]# netstat -natp | egrep '(2888|3888)'
tcp6 0 0 192.168.88.103:3888 :::* LISTEN 1415/java
tcp6 0 0 192.168.88.103:38244 192.168.88.102:2888 ESTABLISHED 1415/java
tcp6 0 0 192.168.88.103:3888 192.168.88.101:39738 ESTABLISHED 1415/java
tcp6 0 0 192.168.88.103:3888 192.168.88.102:37466 ESTABLISHED 1415/java
tcp6 0 0 192.168.88.103:58344 192.168.88.104:3888 ESTABLISHED 1415/java
# q104信息
[root@q104 ~]# netstat -natp | egrep '(2888|3888)'
tcp6 0 0 192.168.88.104:3888 :::* LISTEN 1333/java
tcp6 0 0 192.168.88.104:3888 192.168.88.101:47726 ESTABLISHED 1333/java
tcp6 0 0 192.168.88.104:3888 192.168.88.102:50104 ESTABLISHED 1333/java
tcp6 0 0 192.168.88.104:3888 192.168.88.103:58344 ESTABLISHED 1333/java
tcp6 0 0 192.168.88.104:48034 192.168.88.102:2888 ESTABLISHED 1333/java
通過上面的網絡信息整體網絡連接圖如下
上圖說明:
zookeeper Leader和Follower連接主要通過2888和3888,2888主要用於leader與follower進行工作,3888用於Leader選舉。每個zookeeper節點都會與其他節點建立連接。
4.3.2 選舉流程
情況一:第一次啟動
以上面我們搭好的4台節點為例,myid和zxid情況如下:
節點名稱 | myid | zxid |
---|---|---|
q101 | 4 | 0 |
q102 | 3 | 0 |
q103 | 2 | 0 |
q104 | 1 | 0 |
選舉前提當投票數量超過半數時才有效,此處4台節點,當第一次啟動時候zxid都為0。只要有3台節點啟動就可以選舉出Leader。雖然說q101的myid=4但是如果最后啟動q101,那么第一次啟動時一定是q102成為Leader。
情況二:zxid不為0時,當集群重啟,或Leader掛了的時候
當Leader掛了后,zookeeper集群會進行推選制選舉,會優先選舉數據最全的節點作為Leader(通過比較zxid,zxid越大代表該節點數據最全),如果zxid相同再比較myid。
節點名稱 | myid | zxid |
---|---|---|
q101 | 4 | 0x100000009 |
q103 | 2 | 0x100000010 |
q104 | 1 | 0x100000010 |
原Leader q102掛掉后無論哪個節點先發現Leader掛掉,都會推選zxid最大的節點,q103和q104 zxid 都為xxx10再比較myid。所以此處q103最終會被選為新的Leader。
5 watch
- Watch是輕量級的,其實就是本地JVM的Callback,服務器端只是存了是否有設置了Watcher的布爾類型。
- 在服務端,在FinalRequestProcessor處理對應的Znode操作時,會根據客戶端傳遞的watcher變量,添加到對應的ZKDatabase(org.apache.zookeeper.server.ZKDatabase)中進行持久化存儲,同時將自己NIOServerCnxn做為一個Watcher callback,監聽服務端事件變化
- Leader通過投票通過了某次Znode變化的請求后,然后通知對應的Follower,Follower根據自己內存中的zkDataBase信息,發送notification信息給zookeeper客戶端。
- Zookeeper客戶端接收到notification信息后,找到對應變化path的watcher列表,挨個進行觸發回調。
6 zookeeper實現分布式鎖
6.1 實現原理
多個節點同時在一個指定的節點下面創建臨時會話順序節點,誰創建的節點序號最小,誰就獲得了鎖,並且其他節點就會監聽序號比自己小的節點,一旦序號比自己小的節點被刪除了,其他節點就會得到相應的事件,然后查看自己是否為序號最小的節點,如果是,則獲取鎖。