首發CSDN:徐同學呀,原創不易,轉載請注明源鏈接,我是徐同學,用心輸出高質量文章,希望對你有所幫助, 快來和我一起學習ZooKeeper吧!
文章目錄
- 一、前言
- 二、Zookeeper一個動物園管理員的職責
- 1、作業原理
- 2、功能特點
- 3、資料結構
- 三、Zookeeper的應用場景
- 1、配置管理
- 2、命名服務
- (1)分布式API目錄JNDI
- (2)分布式ID生成器
- 3、服務注冊中心
- 4、服務上下線感知
- 5、集群通信與控制子系統
- 6、分布式鎖
- 四、ZooKeeper下載與安裝
- 1、下載與安裝
- 2、配置說明
- 五、ZooKeeper集群搭建
- 1、偽集群
- (1)準備myid檔案
- (2)配置zoo-.cfg檔案
- 2、集群啟動第一次Leader選舉
- 3、模擬服務實體宕機leader重選
- 4、撰寫集群批量啟動腳本
- 六、要點回顧
- 七、參考文獻
一、前言
ZooKeeper是一個可靠的、強一致性的、沒有單點故障的分布式資料存盤系統,因為其出色的設計(ZAB一致性演算法,Watch機制等)和協調能力,使得它成為很多分布式系統的基石,就像其名稱動物園管理員一樣,做著比較基層的管理協調作業,比如配置管理,命名服務,服務注冊發現等,

二、Zookeeper一個動物園管理員的職責
1、作業原理
Zookeeper的兩個核心設計:Watch機制和ZAB一致性演算法,
Watch機制,可以理解為事件監聽模式,Zookeeper負責存盤資料,并暴露各種事件注冊介面,使用者向Zookeeper注冊關心的事件監聽器,比如監聽節點的創建、修改、洗掉等,一旦監聽的節點資料狀態發生變化,Zookeeper就會通知回呼注冊在Zookeeper上的監聽器做出相應的反應,Zab借鑒Paxos演算法,特意為Zookeeper設計的支持崩潰恢復的原子廣播協議,基于該協議,Zookeeper設計了Leader-Follower機制,只有Leader負責寫請求,然后由Leader向Follower發起寫同步,
2、功能特點
Zookeeper保證的是CP,P(Partition Tolerance磁區容錯性)是必須要有的,所以只能從C(Consistency一致性)和A(Available可用性)中抉擇,魚和熊掌不可兼得,Zookeeper選擇了C,Zookeeper可以保證資料是一致的,無法保證服務一直處于可用狀態,可能會因為網路原因導致一些請求丟失,服務宕機超過半數等,同時在Leader選舉時對外也是不可用的,
- 一個領導者(
Leader),多個跟隨者(Follower),Leader負責讀寫,并向Flower發起寫同步指令,Follower只負責讀,如果Follower接收到寫請求,需要轉發給Leader, - 集群中只要有半數以上節點存活,
Zookeeper集群就能正常服務,所以Zookeeper適合安裝奇數臺服務器, - 資料全域強一致性,每個
Server保存一份相同的資料副本,Client無論連接到哪個Server,資料都是一致的, - 更新請求順序執行,從
Flower轉發的寫請求或者Leader自己接收的寫請求按先后順序執行, - 資料更新原子性,一次資料更新要么成功,要么失敗,
- 實時性,在一定時間范圍內,
Client能讀到最新資料,
3、資料結構
ZooKeeper的資料結構是類UNIX檔案系統目錄樹結構,其中,每個節點稱為ZNode,每個ZNode通過其路徑(Path)唯一標識,每個ZNode可以存盤少量二進制資料,默認最大為1MB,通常不建議在ZNode上存盤大量的資料,因為在資料量比較大時,資料在多節點同步的性能較低,

ZNode有四種型別,節點型別不同,生命周期也不同:
- 持久節點(
PERSISTENT),節點在創建后會一直存在,即使客戶端斷開連接,zk重啟都不會洗掉該節點,直到有洗掉操作來主動洗掉這個節點,持久節點可以持久保存資料,最典型的場景是存盤配置資訊,集合Watch機制,可以實作配置資訊實時生效的特性, - 臨時節點(
EPHEMERAL),臨時節點的生命周期和創建這個節點的客戶端會話系結,也就是說,如果客戶端會話失效(客戶端宕機或下線),這個節點就被自動洗掉,臨時節點跟隨端客戶端啟動創建和客戶端停止而洗掉,典型場景是感知服務的上下線,判斷服務是否存活, - 時序節點(
SEQUENTIAL),創建節點時,節點的名稱會自動加上一個遞增數字后綴,這個數字后綴的范圍為整型的最大值,該節點也是持久節點,每個節點都有唯一編號,類似于資料庫的自增長主鍵,典型場景是生成全域唯一的分布式Id, - 臨時時序節點(
EPHEMERAL_SEQUENTIAL),同時具備臨時節點與時序節點的特性,可用于分布式鎖的實作,

三、Zookeeper的應用場景
ZooKeeper的應用得益于它的節點存盤特性和Watch機制,比較典型的幾個應用場景,如配置管理,命名服務,服務注冊中心,服務上下線感知,集群通信與控制子系統,分布式鎖,分布式Id等,
1、配置管理
配置管理利用ZooKeeper的持久節點存盤配置資訊,結合Watch機制,實作配置資訊實時生效的特性,配置管理在實際生產中叫配置中心,可以統一管理幾乎所有的可配置的,動態的配置資訊,配置中心的應用也是ZooKeeper最廣泛、最基礎的使用場景,
推薦一個基于ZooKeeper實作的,可直接用于生產的配置中心開源框架Qihoo360/QConf,好用,高性能,支持多種語言SDK,
2、命名服務
命名服務是為系統中的資源提供標識能力,ZooKeeper的命名服務主要是利用ZooKeeper節點的樹狀分層結構(路徑唯一)和子節點的順序維護能力,來為分布式系統中的資源命名,
用到命名服務的典型場景有:分布式API目錄JNDI,分布式ID生成器,分布式節點命名等,
(1)分布式API目錄JNDI
著名的Dubbo分布式框架就是利用ZooKeeper實作分布式的JNDI(Java Naming and Directory Interface)功能:
- 服務提供者(Service Provider)在啟動的時候,向
ZooKeeper上的指定節點/dubbo/${serviceName}/providers寫入自己的API地址,這個操作就相當于服務的公開, - 服務消費者(
Consumer)啟動的時候,訂閱節點/dubbo/{serviceName}/providers下的服務提供者的URL地址,獲得所有服務提供者的API,
(2)分布式ID生成器
分布式ID主要用于分布式系統中對資料做全域唯一標識,典型的應用場景是,呼叫鏈路追蹤,分布式ID的生成方式也很多,UUID、資料庫自增(mysql,redis等)、雪花演算法等,
ZooKeeper是個天生的發號器,其時序節點(無論持久和臨時),每個節點都會為它的第一級子節點維護一份遞增編號,且是分布式全域唯一的,例如,在創建節點時,傳入節點前綴/myID/id_,ZooKeeper自動會在/myID/id_后面補充數字順序,例如/myID/id_00000001,
根據這個特性,可以簡單實作一個分布式Id生成器,比較著名的雪花演算法,可以利用ZooKeeper來動態管理機器Id,
3、服務注冊中心
服務注冊中心,其實也是命名服務的一種應用,即用ZooKeeper來存盤服務的注冊資訊,服務的注冊資訊有:服務名稱,服務型別,服務的IP、埠,一個服務可以有多個實體,每個實體為一個節點,節點有狀態(實體運行狀態),權重、型別(實體角色型別)等,

服務注冊中心是服務注冊發現架構實作的基礎,這個架構中還有兩個非常重要的角色:服務提供者和服務消費者,

服務提供者在啟動時,將自身的服務資訊注冊到服務注冊中心,服務消費者啟動時,會主動從服務注冊中心拉取感興趣的服務資訊,ZooKeeper作為服務注冊中心還有一個好處是,可以利用Watch機制,當服務注冊資訊更新時,及時通知服務消費者拉取最新資料,
一般服務消費者不會直接連注冊中心,而是異步監聽注冊中心的變化,并將注冊資訊拉取到本地快取,這樣可以大大降低ZooKeeper的壓力,推薦使用 Qihoo360/QConf 實作注冊中心,
4、服務上下線感知
服務上下線感知,主要利用ZooKeeper的臨時節點實作,應服務在啟動時會將自己的IP地址作為臨時節點創建在某個節點(如/Cluster)下,當服務因為某些原因如斷網或者宕機,使得它與ZooKeeper的會話過期時,這個臨時節點就會被洗掉,可以通過這個特性來感知服務的集群有哪些機器可用了,
同時,可以對這些臨時節點做監聽,感知服務的上下線,并作出相應反應,
5、集群通信與控制子系統
利用ZooKeeper的Watch機制,可以很容易實作集群間的高效通信和控制子系統,

- 在
ZooKeeper里規劃一個用于存放控制命令和應答的·路徑(如/Commands); - 集群中的所有節點在啟動后都監聽此路徑(
/Commands); - 命令列程式(CLI)發給集群節點的命令及引數被包裝成一個
ZNode節點(如上圖中的command1),寫入/Commands路徑下,同時在command1上監聽事件, - 集群中的所有節點都通過
/Commands上的Watch事件收到此命令,然后開始執行command1命令對應的邏輯; - 在某個節點執行完成后就在
command1路徑下新建一個ZNode節點(如node1result)作為應答, - 由于CLI之前在
command1上監聽,所以很快就被通知此命令已經有節點執行完成,CLI就可以實時輸出結果,在所有節點的應答都回傳后(或者等待超時),命令列結束,
6、分布式鎖
使用ZooKeeper實作分布式鎖的原理是利用時序節點和Watch機制:
(1)一個ZNode節點代表一把鎖,如果鎖對應的ZNode節點不存在,那么先創建ZNode節點,需要使用分布式鎖功能先申請一個業務路徑,如/lock/app1(不同服務和不同業務隔離),如果不區分鎖的業務路徑,也需要區分鎖的前綴,
(2)搶占鎖的所有客戶端,使用鎖的ZNode節點的子節點串列來表示;如果某個客戶端需要占用鎖,則在/lock/app1下創建一個臨時有序的子節點,
(3)如何判斷客戶端是否占有鎖?客戶端創建子節點后,需要判斷自己創建的子節點是否為當前子節點串列中序號最小的子節點,如果是,則認為加鎖成功;如果不是,則監聽前一個ZNode子節點的變更訊息,等待前一個節點釋放鎖,
(4)一旦佇列中后面的節點獲得前一個子節點的變更通知,則開始進行判斷,判斷自己是否為當前子節點串列中序號最小的子節點,如果是,則認為加鎖成功;如果不是,則持續監聽,一直到獲得鎖,
(5)獲取鎖后,開始處理業務流程,在完成業務流程后,洗掉對應的子節點,完成釋放鎖的作業,以便后面的節點監聽到節點的變更通知,獲得分布式鎖,
ZooKeeper實作的分布式鎖是公平的,且一般也會是可重入的,流程上非常類似AQS的實作,也是CLH的變體,只不過釋放鎖后節點通知不是本地,而是利用ZooKeeper的Watch機制遠程通知,

四、ZooKeeper下載與安裝
為了后續的原始碼學習,體驗到最新的功能,這里下載最新版本的ZooKeeper(至今最新3.7.0),實際生產中可以安裝歷史版本,
(安裝ZK前,請先安裝JDK)
1、下載與安裝
https://zookeeper.apache.org/



復制下載鏈接,到Centos中下載安裝,
cd /usr/local/
mkdir zookeeper
cd zookeeper
# 下載
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7.0-bin.tar.gz
# 解壓在當前目錄
tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz
下載的zk安裝,解壓之后就可以用了:

# 修改解壓之后的目錄名稱(也可以不修改)
mv apache-zookeeper-3.7.0-bin apache-zookeeper-3.7.0
cd apache-zookeeper-3.7.0
# data/zoo用于存盤zk資料
mkdir -p data/zoo
# 修改zoo_sample.cfg 為zoo.cfg,因為bin/zkServer.sh默認用的是zoo.cfg
mv conf/zoo_sample.cfg conf/zoo.cfg
# 修改zoo.cfg 中的dataDir為/usr/local/zookeeper/apache-zookeeper-3.7.0/data/zoo
# 不能用/tmp 存盤zk資料,因為機器會定時清理/tmp目錄下的資料
vim conf/zoo.cfg

# 啟動,默認使用的conf/zoo.cfg啟動,單機版,
bin/zkServer.sh start

直接運行bin/zkServer.sh,可以看到該命令的使用說明,常用的有start,status,restart,stop,還可以在啟動時指定組態檔,如:
# 指定組態檔啟動,組態檔路徑必須放在后面
bin/zkServer.sh start conf/zoo.cfg
2、配置說明
ZooKeeper的啟動組態檔zoo.cfg的配置內容說明:
# The number of milliseconds of each tick
# 通信心跳時間,Leader和Follower或者Zookeeper服務器與客戶端心跳時間,單位毫秒
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
# Leader和Follower初始連接時能容忍的最多心跳數(tickTime的數量)
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
# Leader和Follower之間通信時間如果超過syncLimit * tickTime,
# Leader認為Follwer死掉,從服務器串列中洗掉Follwer,
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 保存zk資料的目錄,默認是/tmp,一般要修改為其他路徑,因為Linux系統會定期請求/tmp下的資料
dataDir=/usr/local/zookeeper/apache-zookeeper-3.7.0/data/zoo
# 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
# 最大客戶端連接數,默認60
#maxClientCnxns=60
# The number of snapshots to retain in dataDir
# dataDir中保留的快照數量
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
# 自動清除任務運行時間間隔,單位為小時,
# 若設定為 0 則禁用自動清除功能
#autopurge.purgeInterval=1
# zk 指標監控提供者,官方推薦用prometheus普羅米修斯
## Metrics Providers
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
zoo.cfg配置內容最后面是ZooKeeper指標資料監控配置,官方推薦用prometheus,如果用生產中用influxdb存盤zk指標資料,本人推薦用Telegraf,

五、ZooKeeper集群搭建
ZooKeeper集群中只要有半數以上節點存活,就能正常對外提供服務,所以Zookeeper集群一般運行奇數個服務端,由于學習用的Linux只有一臺,只能搭建一個偽集群,不過理論上都是一樣的,
1、偽集群
搭建ZooKeeper集群至少需要同時運行三個zk服務實體,如果其中一個掛了,依然可以正常對外提供服務,
(1)準備myid檔案
為三個ZK實體創建資料存盤目錄,
# 在/usr/local/zookeeper/apache-zookeeper-3.7.0/data目錄下
# 創建zoo-1、zoo-2、zoo-3
mkdir data/zoo-1 && mkdir data/zoo-2 && mkdir data/zoo-1
為三個目錄分別創建myid檔案,其內容只有一個數字編號,分別為1、2、3,myid檔案中的編號唯一標識服務節點,用于Leader投票選舉,
(2)配置zoo-.cfg檔案
復制修改三份zoo.cfg,分別為zoo-1.cfg、zoo-2.cfg、zoo-3.cfg,
# zoo-1.cfg
# 沒有顯示的配置項和zoo.cfg一致
dataDir=/usr/local/zookeeper/apache-zookeeper-3.7.0/data/zoo-1
clientPort=2191
server.1 = 127.0.0.1:2888:3888
server.2 = 127.0.0.1:2889:3889
server.3 = 127.0.0.1:2890:3890
# zoo-2.cfg
# 沒有顯示的配置項和zoo.cfg一致
dataDir=/usr/local/zookeeper/apache-zookeeper-3.7.0/data/zoo-2
clientPort=2192
server.1 = 127.0.0.1:2888:3888
server.2 = 127.0.0.1:2889:3889
server.3 = 127.0.0.1:2890:3890
# zoo-3.cfg
# 沒有顯示的配置項和zoo.cfg一致
dataDir=/usr/local/zookeeper/apache-zookeeper-3.7.0/data/zoo-3
clientPort=2193
server.1 = 127.0.0.1:2888:3888
server.2 = 127.0.0.1:2889:3889
server.3 = 127.0.0.1:2890:3890
由于三個偽實體在一臺機器上運行,所以clientPort不一樣,實際生產中三個zk實體會運行在不同機器上,clientPort建議一致(2181),
每個配置中都有相同的三行配置,表示這是一個集群關系配置:
server.A=B:C:D
- A是一個數字,就是每個zk實體的myid檔案中的編號,
- B是ip地址,每個zk實體所在的機器ip,
- C是集群中
Leader和Follower通信的埠,如Leader向Follower發送同步寫指令,Follower將寫請求轉發給Leader,(通信埠) - D是集群中用于投票選舉Leader的埠,(選舉埠)
實際生產中,不同的zk實體運行在不同機器上,所以通信埠和選舉埠各需要一個即可,
2、集群啟動第一次Leader選舉
依次執行如下命令,啟動集群:
# 在/usr/local/zookeeper/apache-zookeeper-3.7.0目錄下
bin/zkServer.sh start conf/zoo-1.cfg
bin/zkServer.sh start conf/zoo-2.cfg
bin/zkServer.sh start conf/zoo-3.cfg
為了看清楚三個zk實體啟動時的Leader選舉程序,特意在每次start之后穿插status:
- 啟動
zoo-1時,發起一次投票,zoo-1給自己投一票,此時服務實體不夠半數以上,無法對外提供服務,status顯示Error contacting service, - 啟動
zoo-2時,再發起一次投票,status顯示zoo-2為leader,zoo-1顯示follower,說明zoo-2給自己投了一票,zoo-1也給zoo-2投了一票,服務實體數超過半數,leader選舉完成,此時可以對外提供服務, - 啟動
zoo-3時,status顯示zoo-3是follower,說明后來者zoo-3把選票投給了已經當上了leader的zoo-2,

調整啟動順序,zoo-2,zoo-1,zoo-3:
leader依然是zoo-2,

將zoo-3放在前兩次啟動,leader就是zoo-3,最后啟動實體都是flower(演示省略),可得出結論:
myid小的投票給myid大的實體,- 已經完成
leader選舉后,后來者會直接把選票投給現任leader,自己甘為flower,
(leader投票選舉的更多細節還需要后續看了原始碼才知道,)
3、模擬服務實體宕機leader重選
flower實體宕機,只要集群實體個數在半數以上,依然可以對外提供服務,leader的地位也不會動搖,只有當身為leader的實體宕機了,此時就需要重新投票選leader,
此時leader是zoo-2,將其stop,再次查看zoo-1和zoo-3的狀態,zoo-3變成了leader,

(更多leader重選細節還需后續看了原始碼才知道,)
4、撰寫集群批量啟動腳本
集群中每個實體手動一個個執行啟動命令,確實比較麻煩,特撰寫一個批量啟動腳本,如果多實體運行在不同機器上,可改成ssh的方式:
#!/bin/bash
case $1 in
"start"){
for i in zoo-1.cfg zoo-2.cfg zoo-3.cfg
do
echo ---------- zookeeper $i 啟動 ------------
/usr/local/zookeeper/apache-zookeeper-3.7.0/bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.7.0/conf/$i
done
};;
"stop"){
for i in zoo-1.cfg zoo-2.cfg zoo-3.cfg
do
echo ---------- zookeeper $i 停止 ------------
/usr/local/zookeeper/apache-zookeeper-3.7.0/bin/zkServer.sh stop /usr/local/zookeeper/apache-zookeeper-3.7.0/conf/$i
done
};;
"restart"){
for i in zoo-1.cfg zoo-2.cfg zoo-3.cfg
do
echo ---------- zookeeper $i 重啟 ------------
/usr/local/zookeeper/apache-zookeeper-3.7.0/bin/zkServer.sh restart /usr/local/zookeeper/apache-zookeeper-3.7.0/conf/$i
done
};;
"status"){
for i in zoo-1.cfg zoo-2.cfg zoo-3.cfg
do
echo ---------- zookeeper $i 狀態 ------------
/usr/local/zookeeper/apache-zookeeper-3.7.0/bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.7.0/conf/$i
done
};;
esac

六、要點回顧
ZooKeeper集群搭建好了,就該用客戶端連接上集群進行操作了,后續篇章會對客戶端操作做詳細講解,
本篇總結如下:
Zookeeper有兩個核心設計:Watch機制和ZAB一致性演算法,基于此,Zookeeper成為很多分布式系統的基石,Zookeeper放棄可用性A,保證CP,資料全域強一致性,并且能在幾秒內快速同步,具有不錯的實時性體驗,Zookeeper資料結構是類UNIX檔案系統目錄的樹狀結構,每個ZNode通過其路徑(Path)唯一標識,每個ZNode可以存盤少量二進制資料,默認最大為1MB,ZNode有四種型別:持久節點(配置中心,注冊中心)、臨時節點(服務上下線感知)、時序節點(分布式ID)、臨時時序節點(分布式鎖),Zookeeper的典型的應用場景:配置中心,命名服務,服務注冊中心,服務上下線感知,集群通信與控制系統,分布式鎖等,- 在
leader選舉沒有完成前,選票會投給myid較大的實體,選舉完成后,后來啟動的實體會將選票直接投給現任leader,自己位flower,
后續會通過看ZooKeeper原始碼,更深層次的剖析其實作原理,

七、參考文獻
書籍:《架構解密:從分布式到微服務(第2版)》吳治輝著 -2.3分布式系統的基石之ZooKeeper,
書籍:《Netty、Redis、ZooKeeper高并發實戰》尼恩著 -第10章 ZooKeeper分布式協調,
如若文章有錯誤理解,歡迎批評指正,同時非常期待你的留言和點贊,如果覺得有用,不妨來個一鍵三連,讓更多人受益,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299948.html
標籤:其他
