rodert教你學(xué)Zookeeper-實(shí)戰(zhàn)這一篇就夠了
前言
1. 什么是Zookeeper
1.2.Zookeeper簡介
1.3.為什么要用Zookeeper
2. Zookeeper介紹
2.1 **百度百科**
2.2. 文件系統(tǒng)
2.3. 監(jiān)聽通知機(jī)制
3. Zookeeper整體架構(gòu)
4. 快速入門(quick start)
4.1.安裝
4.2.啟動
4.3.查詢
5. 常用指令
6. 應(yīng)用場景
7. 選舉機(jī)制
8. 三大功能
9. Java Api 操作zookeeper
前言
聲明:參考來源互聯(lián)網(wǎng),有任何爭議可以留言。站在前人的肩上,我們才能看的更遠(yuǎn)。
本教程純手打,致力于最實(shí)用教程,不需要什么獎勵,只希望多多轉(zhuǎn)發(fā)支持。 歡迎來我公眾號,希望可以結(jié)識你,你有什么想看的可以催更,微信搜索:[JavaPub]
有任何問題都可以來談?wù)?,等你哦?/p>

如果你對zookeeper有一定了解,那么直接跳到你需要的知識點(diǎn)。
1. 什么是Zookeeper
1.2.Zookeeper簡介
ZooKeeper: A Distributed Coordination Service for Distributed ApplicationsZooKeeper is a distributed, open-source coordination service for distributed applications. It exposes a simple set of primitives that distributed applications can build upon to implement higher level services for synchronization, configuration maintenance, and groups and naming. It is designed to be easy to program to, and uses a data model styled after the familiar directory tree structure of file systems. It runs in Java and has bindings for both Java and C. Coordination services are notoriously hard to get right. They are especially prone to errors such as race conditions and deadlock. The motivation behind ZooKeeper is to relieve distributed applications the responsibility of implementing coordination services from scratch. https://zookeeper.apache.org/doc/current/zookeeperOver.html
官網(wǎng)地址:https://zookeeper.apache.org/doc/r3.4.12/index.html
上邊是Zookeeper官網(wǎng)的描述,as everyone knows,ZooKeeper是分布式應(yīng)用程序的分布式協(xié)調(diào)服務(wù)。
1.3.為什么要用Zookeeper
學(xué)習(xí)一個東西,anyhow,知道為什么學(xué)它至關(guān)重要。
看到一個比較靠譜的例子:
一個團(tuán)隊里面,需要一個leader,leader是干嘛用的?管理什么的咱不說,就說如果外面的人,想問關(guān)于這個團(tuán)隊的一切事情,首先就會去找這個leader,因?yàn)樗赖淖疃?,而且他的回答最靠譜。
比如產(chǎn)品經(jīng)理小餅過來要人,作為leader,老呂發(fā)現(xiàn)小耀最近沒有項(xiàng)目安排,于是把小耀安排給了小餅的項(xiàng)目;
過了一會,另一個產(chǎn)品小西也過來要人,老呂發(fā)現(xiàn)剛剛把小耀安排走了,已經(jīng)沒人,于是就跟小西說,人都被你們產(chǎn)品要走了,你們產(chǎn)品自己去協(xié)調(diào)去。
如果老呂這時候忘了小耀已經(jīng)被安排走了,把小耀也分配給小西,那到時兩個產(chǎn)品就要打架了。
這就是leader在團(tuán)隊里的【協(xié)調(diào)作用】。
同樣的,在分布式系統(tǒng)中,也需要這樣的協(xié)調(diào)者,來回答系統(tǒng)下各個節(jié)點(diǎn)的提問。
Zookeeper能完美解決分布式協(xié)調(diào)服務(wù)這個問題
但是這個例子屬于單機(jī)模式,當(dāng)我們擴(kuò)展為三臺服務(wù)器集群,小西過來問leader02要人,這時leader們信息還沒有同步。
這時就會涉及到Zookeeper的其他幾點(diǎn)特性:
1、配置信息同步
2、分布式鎖控制
3、消息的發(fā)布與訂閱(典型的生產(chǎn)者消費(fèi)者模型)
4、集群內(nèi)節(jié)點(diǎn)狀態(tài)的快速感知
當(dāng)信息還沒有同步完成時,不對外提供服務(wù),阻塞住查詢請求,等待信息同步完成,再給查詢請求返回信息。
這樣的系統(tǒng),就叫分布式協(xié)調(diào)系統(tǒng)。誰能把這個數(shù)據(jù)同步的時間壓縮的更短,誰的請求響應(yīng)就更快,誰就更出色,Zookeeper就是其中的佼佼者。
**它用起來像單機(jī)一樣,能夠提供數(shù)據(jù)強(qiáng)一致性,但是其實(shí)背后是多臺機(jī)器構(gòu)成的集群,不會有SPOF。**單點(diǎn)故障
2. Zookeeper介紹
2.1 百度百科
ZooKeeper是一個分布式的,開放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個開源的實(shí)現(xiàn),是Hadoop和Hbase的重要組件。它是一個為分布式應(yīng)用提供一致性服務(wù)的軟件,提供的功能包括:配置維護(hù)、域名服務(wù)、分布式同步、組服務(wù)等。 ZooKeeper的目標(biāo)就是封裝好復(fù)雜易出錯的關(guān)鍵服務(wù),將簡單易用的接口和性能高效、功能穩(wěn)定的系統(tǒng)提供給用戶。 ZooKeeper包含一個簡單的原語集,提供Java和C的接口。 ZooKeeper代碼版本中,提供了分布式獨(dú)享鎖、選舉、隊列的接口,代碼在$zookeeper_home\src\recipes。其中分布鎖和隊列有Java和C兩個版本,選舉只有Java版本。
看了上面的介紹,你還不知道zookeeper是什么,那么簡單來說:zookeeper=文件系統(tǒng)+監(jiān)聽通知機(jī)制。
包含如下四種節(jié)點(diǎn):臨時節(jié)點(diǎn)(EPHEMERAL)、永久節(jié)點(diǎn)(persistent)、有編號節(jié)點(diǎn)(Persistent_sequential)、臨時有編號(Ephemral_ sequential)
2.2. 文件系統(tǒng)
Zookeeper維護(hù)一個類似文件系統(tǒng)的數(shù)據(jù)結(jié)構(gòu):

2.3. 監(jiān)聽通知機(jī)制
客戶端注冊監(jiān)聽它關(guān)心的目錄節(jié)點(diǎn),當(dāng)目錄節(jié)點(diǎn)發(fā)生變化(數(shù)據(jù)改變、被刪除、子目錄節(jié)點(diǎn)增加刪除)時,zookeeper會通知客戶端。
3、 Zookeeper能做什么 zookeeper功能非常強(qiáng)大,可以實(shí)現(xiàn)諸如分布式應(yīng)用配置管理、統(tǒng)一命名服務(wù)、狀態(tài)同步服務(wù)、集群管理等功能,我們這里拿比較簡單的分布式應(yīng)用配置管理為例來說明。
假設(shè)我們的程序是分布式部署在多臺機(jī)器上,如果我們要改變程序的配置文件,需要逐臺機(jī)器去修改,非常麻煩,現(xiàn)在把這些配置全部放到zookeeper上去,保存在 zookeeper 的某個目錄節(jié)點(diǎn)中,然后所有相關(guān)應(yīng)用程序?qū)@個目錄節(jié)點(diǎn)進(jìn)行監(jiān)聽,一旦配置信息發(fā)生變化,每個應(yīng)用程序就會收到 zookeeper 的通知,然后從 zookeeper 獲取新的配置信息應(yīng)用到系統(tǒng)中。
3. Zookeeper整體架構(gòu)

4. 快速入門(quick start)
4.1.安裝
單機(jī)安裝
Zookeeper是解壓包,只需要安裝、解壓就可以啟動使用
wget?https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
tar?-zxf?./zookeeper-3.4.14.tar.gz
在conf目錄下,有默認(rèn)啟動配置文件【zoo_sample.cfg】,復(fù)制一份到同級目錄下【zoo.cfg】。
配置文件解讀:
#?tickTime這個時間是作為zookeeper服務(wù)器之間或客戶端與服務(wù)器之間維持心跳的時間間隔,也就是說每個tickTime時間就會發(fā)送一個心跳。(以毫秒為單位)
tickTime?=?2000
#?dataDir?ZooKeeper的狀態(tài)存儲位置,看名字就知是數(shù)據(jù)目錄。在你的系統(tǒng)中檢查這個目錄是否存在,如果不存在手動創(chuàng)建,并且給予可寫權(quán)限。
dataDir?=?/path/to/zookeeper/data
#?這個端口就是客戶端連接Zookeeper服務(wù)器的端口,Zookeeper會監(jiān)聽這個端口接受客戶端的訪問請求;
clientPort?=?2181
#?initLimit這個配置項(xiàng)是用來配置zookeeper接受客戶端(這里所說的客戶端不是用戶連接zookeeper服務(wù)器的客戶端,而是zookeeper服務(wù)器集群中連接到leader的follower?服務(wù)器)初始化連接時最長能忍受多少個心跳時間間隔數(shù)。
#?當(dāng)已經(jīng)超過10個心跳的時間(也就是tickTime)長度后?zookeeper?服務(wù)器還沒有收到客戶端的返回信息,那么表明這個客戶端連接失敗??偟臅r間長度就是?5*2000=10秒。
initLimit?=?5
#?syncLimit這個配置項(xiàng)標(biāo)識leader與follower之間發(fā)送消息,請求和應(yīng)答時間長度,最長不能超過多少個tickTime的時間長度,總的時間長度就是2*2000=4秒
syncLimit?=?2
#?日志存放的位置
dataLogDir=/path/to/zookeeper/log
#?2888,3888?are?election?port
#?2888端口是zookeeper服務(wù)之間的通訊的端口,3888是zookeeper與其他應(yīng)用程序通訊的端口。
#?server.A=B:C:D中的A是一個數(shù)字,表示這個是第幾號服務(wù)器,B是這個服務(wù)器的IP地址,C第一個端口用來集群成員的信息交換,表示這個服務(wù)器與集群中的leader服務(wù)器交換信息的端口,D是在leader掛掉時專門用來進(jìn)行選舉leader所用的端口。
server.1=localhost:2888:3888
集群安裝
本例搭建的是偽集群模式,即一臺機(jī)器上啟動三個zookeeper實(shí)例組成集群,真正的集群模式無非就是實(shí)例IP地址不同,搭建方法沒有區(qū)別
配置說明
tickTime:這個時間是作為 Zookeeper 服務(wù)器之間或客戶端與服務(wù)器之間維持心跳的時間間隔,也就是每個 tickTime 時間就會發(fā)送一個心跳。
initLimit:這個配置項(xiàng)是用來配置 Zookeeper 接受客戶端(這里所說的客戶端不是用戶連接 Zookeeper 服務(wù)器的客戶端,而是 Zookeeper 服務(wù)器集群中連接到 Leader 的 Follower 服務(wù)器)初始化連接時最長能忍受多少個心跳時間間隔數(shù)。當(dāng)已經(jīng)超過 10個心跳的時間(也就是 tickTime)長度后 Zookeeper 服務(wù)器還沒有收到客戶端的返回信息,那么表明這個客戶端連接失敗??偟臅r間長度就是 10*2000=20 秒
syncLimit:這個配置項(xiàng)標(biāo)識 Leader 與 Follower 之間發(fā)送消息,請求和應(yīng)答時間長度,最長不能超過多少個 tickTime 的時間長度,總的時間長度就是 5*2000=10秒
dataDir:顧名思義就是 Zookeeper 保存數(shù)據(jù)的目錄,默認(rèn)情況下,Zookeeper 將寫數(shù)據(jù)的日志文件也保存在這個目錄里。
clientPort:這個端口就是客戶端連接 Zookeeper 服務(wù)器的端口,Zookeeper 會監(jiān)聽這個端口,接受客戶端的訪問請求。
server.A=B:C:D:其中 A 是一個數(shù)字,表示這個是第幾號服務(wù)器;B 是這個服務(wù)器的 ip 地址;C 表示的是這個服務(wù)器與集群中的 Leader 服務(wù)器交換信息的端口;D 表示的是萬一集群中的 Leader 服務(wù)器掛了,需要一個端口來重新進(jìn)行選舉,選出一個新的 Leader,而這個端口就是用來執(zhí)行選舉時服務(wù)器相互通信的端口。如果是偽集群的配置方式,由于 B 都是一樣,所以不同的 Zookeeper 實(shí)例通信端口號不能一樣,所以要給它們分配不同的端口號。
偽分布式,三個節(jié)點(diǎn)。
# cp conf/zoo_sample.cfg conf/zoo-1.cfg
# cp conf/zoo_sample.cfg conf/zoo-2.cfg
# cp conf/zoo_sample.cfg conf/zoo-3.cfg
修改dataDir和clientPort不同即可
# vim conf/zoo-2.cfg
dataDir=/tmp/zookeeper-2
clientPort=2182
# vim conf/zoo-2.cfg
dataDir=/tmp/zookeeper-3
clientPort=2183
# cd /tmp/zookeeper-1
# vim myid
# cd /tmp/zookeeper-2
# vim myid
# cd /tmp/zookeeper-3
# vim myid
# bin/zkServer.sh start conf/zoo-1.cfg # bin/zkServer.sh start conf/zoo-2.cfg # bin/zkServer.sh start conf/zoo-3.cfg
至此,集群搭建完成,可以連接試試了
bin/zkServer.sh status conf/zoo-1.cfg
完成
啟動
標(biāo)識Server ID,設(shè)置每個節(jié)點(diǎn)的server id
4.2.啟動
[root@iz2zehz5b1m03ahtrhebcaz?bin]#?./zkServer.sh?
ZooKeeper?JMX?enabled?by?default
Using?config:?/home/soft/zookeeper-3.4.8/bin/../conf/zoo.cfg
Usage:?./zkServer.sh?{start|start-foreground|stop|restart|status|upgrade|print-cmd}
進(jìn)入到bin目錄下,
啟動
./zkServer.sh?start
狀態(tài)
./zkServer.sh?status
4.3.查詢
Zookeeper客戶端連接指令
進(jìn)入zookeeper下bin目錄
[root@iz2zehz5b1m03ahtrhebcaz?bin]#?pwd
/home/soft/zookeeper-3.4.8/bin
[root@iz2zehz5b1m03ahtrhebcaz?bin]#?ll
total?44
-rwxr-xr-x?1?elasticsearch?elasticsearch??232?Feb??6??2016?README.txt
-rwxr-xr-x?1?elasticsearch?elasticsearch?1937?Feb??6??2016?zkCleanup.sh
-rwxr-xr-x?1?elasticsearch?elasticsearch?1056?Feb??6??2016?zkCli.cmd
-rwxr-xr-x?1?elasticsearch?elasticsearch?1534?Feb??6??2016?zkCli.sh
-rwxr-xr-x?1?elasticsearch?elasticsearch?1628?Feb??6??2016?zkEnv.cmd
-rwxr-xr-x?1?elasticsearch?elasticsearch?2696?Feb??6??2016?zkEnv.sh
-rwxr-xr-x?1?elasticsearch?elasticsearch?1089?Feb??6??2016?zkServer.cmd
-rwxr-xr-x?1?elasticsearch?elasticsearch?6773?Feb??6??2016?zkServer.sh
-rw-r--r--?1?root??????????root??????????7850?May??4?13:26?zookeeper.out
可以看到很多腳本文件,通過zkCli.sh連接客戶端
./zkCli.sh -server 127.0.0.1:2181
通過ls /,查看已注冊服務(wù), 例如查詢dubbo
ls /dubbo
可以看到dubbo服務(wù)地外提供的接口
消費(fèi)者、生產(chǎn)者
ls /dubbo/com.ivan.service.provider.UserService/consumers
ls /dubbo/com.ivan.service.provider.UserService/providers
5. 常用指令
客戶端連接后整體使用和linux很相似,上一章做了一些介紹
zookeeper通過 ./bin/zkServer.sh start 命令啟動后,通過客戶端連接
./bin/zkCli.sh -server ip:port
[root@iz2zehz5b1m03ahtrhebcaz?zookeeper-3.4.8]#?./bin/zkCli.sh?-server?127.0.0.1:2181
Connecting?to?127.0.0.1:2181
2020-05-14?13:52:21,530?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:zookeeper.version=3.4.8--1,?built?on?02/06/2016?03:18?GMT
2020-05-14?13:52:21,533?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:host.name=iz2zehz5b1m03ahtrhebcaz
2020-05-14?13:52:21,533?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.version=1.8.0_144
2020-05-14?13:52:21,539?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.vendor=Oracle?Corporation
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.home=/home/soft/java/jdk1.8.0_144/jre
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.class.path=/home/soft/zookeeper-3.4.8/bin/../build/classes:/home/soft/zookeeper-3.4.8/bin/../build/lib/*.jar:/home/soft/zookeeper-3.4.8/bin/../lib/slf4j-log4j12-1.6.1.jar:/home/soft/zookeeper-3.4.8/bin/../lib/slf4j-api-1.6.1.jar:/home/soft/zookeeper-3.4.8/bin/../lib/netty-3.7.0.Final.jar:/home/soft/zookeeper-3.4.8/bin/../lib/log4j-1.2.16.jar:/home/soft/zookeeper-3.4.8/bin/../lib/jline-0.9.94.jar:/home/soft/zookeeper-3.4.8/bin/../zookeeper-3.4.8.jar:/home/soft/zookeeper-3.4.8/bin/../src/java/lib/*.jar:/home/soft/zookeeper-3.4.8/bin/../conf:.:/home/soft/java/jdk1.8.0_144/lib/dt.jar:/home/soft/java/jdk1.8.0_144/lib/tools.jar
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.io.tmpdir=/tmp
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:java.compiler=<NA>
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:os.name=Linux
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:os.arch=amd64
2020-05-14?13:52:21,540?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:os.version=3.10.0-514.26.2.el7.x86_64
2020-05-14?13:52:21,541?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:user.name=root
2020-05-14?13:52:21,541?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:user.home=/root
2020-05-14?13:52:21,541?[myid:]?-?INFO??[main:Environment@100]?-?Client?environment:user.dir=/home/soft/zookeeper-3.4.8
2020-05-14?13:52:21,542?[myid:]?-?INFO??[main:ZooKeeper@438]?-?Initiating?client?connection,?connectString=127.0.0.1:2181?sessionTimeout=30000?watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@799f7e29
Welcome?to?ZooKeeper!
JLine?support?is?enabled
2020-05-14?13:52:21,579?[myid:]?-?INFO??[main-SendThread(127.0.0.1:2181):ClientCnxn$SendThread@1032]?-?Opening?socket?connection?to?server?127.0.0.1/127.0.0.1:2181.?Will?not?attempt?to?authenticate?using?SASL?(unknown?error)
2020-05-14?13:52:21,688?[myid:]?-?INFO??[main-SendThread(127.0.0.1:2181):ClientCnxn$SendThread@876]?-?Socket?connection?established?to?127.0.0.1/127.0.0.1:2181,?initiating?session
2020-05-14?13:52:21,709?[myid:]?-?INFO??[main-SendThread(127.0.0.1:2181):ClientCnxn$SendThread@1299]?-?Session?establishment?complete?on?server?127.0.0.1/127.0.0.1:2181,?sessionid?=?0x171de1d5d640005,?negotiated?timeout?=?30000
WATCHER::
WatchedEvent?state:SyncConnected?type:None?path:null
查看當(dāng)前包含的內(nèi)容
[zk:?127.0.0.1:2181(CONNECTED)?2]?ls?/
[cluster,?controller_epoch,?brokers,?zookeeper,?admin,?isr_change_notification,?consumers,?log_dir_event_notification,?latest_producer_id_block,?config]
創(chuàng)建一個 znode ,使用create /zkPro myData
[zk:?127.0.0.1:2181(CONNECTED)?3]?create?/zkPro?MyData
Created?/zkPro
查一下創(chuàng)建的內(nèi)容
[zk:?127.0.0.1:2181(CONNECTED)?4]?ls?/????????????????
[cluster,?controller_epoch,?brokers,?zookeeper,?zkPro,?admin,?isr_change_notification,?consumers,?log_dir_event_notification,?latest_producer_id_block,?config]
我們運(yùn)行 get 命令來確認(rèn)第二步中所創(chuàng)建的 znode 是否包含我們所創(chuàng)建的字符串:
[zk:?127.0.0.1:2181(CONNECTED)?5]?get?/zkPro?MyData
MyData
cZxid?=?0x338
ctime?=?Thu?May?14?13:57:29?CST?2020
mZxid?=?0x338
mtime?=?Thu?May?14?13:57:29?CST?2020
pZxid?=?0x338
cversion?=?0
dataVersion?=?0
aclVersion?=?0
ephemeralOwner?=?0x0
dataLength?=?6
numChildren?=?0
通過 set 命令來對 zk 所關(guān)聯(lián)的字符串進(jìn)行設(shè)置:
WatchedEvent?state:SyncConnected?type:NodeDataChanged?path:/zkPro
cZxid?=?0x338
ctime?=?Thu?May?14?13:57:29?CST?2020
mZxid?=?0x339
mtime?=?Thu?May?14?14:06:01?CST?2020
pZxid?=?0x338
cversion?=?0
dataVersion?=?1
aclVersion?=?0
ephemeralOwner?=?0x0
dataLength?=?9
numChildren?=?0
刪除節(jié)點(diǎn)
[zk:?127.0.0.1:2181(CONNECTED)?7]?delete?/zkPro
[zk:?127.0.0.1:2181(CONNECTED)?8]?
6. 應(yīng)用場景
場景一 配置文件
我們在開發(fā)的時候,有時候需要獲取一些公共的配置,比如數(shù)據(jù)庫連接信息等,并且偶然可能需要更新配置。如果我們的服務(wù)器有N多臺的話,那修改起來會特別的麻煩,并且還需要重新啟動。這里Zookeeper就可以很方便的實(shí)現(xiàn)類似的功能。
場景二 分布式鎖
在我們?nèi)粘5拈_發(fā)中,如果是單個進(jìn)程中對共享資源的訪問,我們只需要用synchronized或者lock就能實(shí)現(xiàn)互斥操作。但是對于跨進(jìn)程、跨主機(jī)、跨網(wǎng)絡(luò)的共享資源似乎就無能為力了。
場景三 分布式隊列
在日常使用中,特別是像生產(chǎn)者消費(fèi)者模式中,經(jīng)常會使用BlockingQueue來充當(dāng)緩沖區(qū)的角色。但是在分布式系統(tǒng)中這種方式就不能使用BlockingQueue來實(shí)現(xiàn)了,但是Zookeeper可以實(shí)現(xiàn)。
場景四 負(fù)載均衡
首先我們需要簡單的理解分布式和集群,通俗點(diǎn)說:分布式就是將一個系統(tǒng)拆分到多個獨(dú)立運(yùn)行的應(yīng)用中(有可能在同一臺主機(jī)也有可能在不同的主機(jī)上),集群就是將單個獨(dú)立的應(yīng)用復(fù)制多分放在不同的主機(jī)上來減輕服務(wù)器的壓力。而Zookeeper不僅僅可以作為分布式集群的服務(wù)注冊調(diào)度中心(例如dubbo),也可以實(shí)現(xiàn)集群的負(fù)載均衡。
Zookeeper是一個功能非常強(qiáng)大的應(yīng)用,除了上面幾種應(yīng)用外,還有命名服務(wù)、分布式協(xié)調(diào)通知等也是常用的場景。
7. 選舉機(jī)制
選舉機(jī)制,顧名思義就是投票選舉。
分布式集群開發(fā)的目的就是為了保證系統(tǒng)的穩(wěn)定運(yùn)行,如果有一個服務(wù)掛掉,不會對整個系統(tǒng)造成大的影響。
Leader選舉是保證分布式數(shù)據(jù)一致性的關(guān)鍵所在。當(dāng)Zookeeper集群中的一臺服務(wù)器出現(xiàn)以下兩種情況之一時,需要進(jìn)入Leader選舉。
服務(wù)器初始化啟動。
服務(wù)器運(yùn)行期間無法和Leader保持連接。
情況一
1.?服務(wù)器啟動時期的Leader選舉
??若進(jìn)行Leader選舉,則至少需要兩臺機(jī)器,這里選取3臺機(jī)器組成的服務(wù)器集群為例。在集群初始化階段,當(dāng)有一臺服務(wù)器Server1啟動時,其單獨(dú)無法進(jìn)行和完成Leader選舉,當(dāng)?shù)诙_服務(wù)器Server2啟動時,此時兩臺機(jī)器可以相互通信,每臺機(jī)器都試圖找到Leader,于是進(jìn)入Leader選舉過程。選舉過程如下
??(1)?每個Server發(fā)出一個投票。由于是初始情況,Server1和Server2都會將自己作為Leader服務(wù)器來進(jìn)行投票,每次投票會包含所推舉的服務(wù)器的myid和ZXID,使用(myid,?ZXID)來表示,此時Server1的投票為(1,?0),Server2的投票為(2,?0),然后各自將這個投票發(fā)給集群中其他機(jī)器。
??(2)?接受來自各個服務(wù)器的投票。集群的每個服務(wù)器收到投票后,首先判斷該投票的有效性,如檢查是否是本輪投票、是否來自LOOKING狀態(tài)的服務(wù)器。
??(3)?處理投票。針對每一個投票,服務(wù)器都需要將別人的投票和自己的投票進(jìn)行PK,PK規(guī)則如下
????·?優(yōu)先檢查ZXID。ZXID比較大的服務(wù)器優(yōu)先作為Leader。
????·?如果ZXID相同,那么就比較myid。myid較大的服務(wù)器作為Leader服務(wù)器。
??對于Server1而言,它的投票是(1,?0),接收Server2的投票為(2,?0),首先會比較兩者的ZXID,均為0,再比較myid,此時Server2的myid最大,于是更新自己的投票為(2,?0),然后重新投票,對于Server2而言,其無須更新自己的投票,只是再次向集群中所有機(jī)器發(fā)出上一次投票信息即可。
??(4)?統(tǒng)計投票。每次投票后,服務(wù)器都會統(tǒng)計投票信息,判斷是否已經(jīng)有過半機(jī)器接受到相同的投票信息,對于Server1、Server2而言,都統(tǒng)計出集群中已經(jīng)有兩臺機(jī)器接受了(2,?0)的投票信息,此時便認(rèn)為已經(jīng)選出了Leader。
??(5)?改變服務(wù)器狀態(tài)。一旦確定了Leader,每個服務(wù)器就會更新自己的狀態(tài),如果是Follower,那么就變更為FOLLOWING,如果是Leader,就變更為LEADING。
情況二
?2.?服務(wù)器運(yùn)行時期的Leader選舉
??在Zookeeper運(yùn)行期間,Leader與非Leader服務(wù)器各司其職,即便當(dāng)有非Leader服務(wù)器宕機(jī)或新加入,此時也不會影響Leader,但是一旦Leader服務(wù)器掛了,那么整個集群將暫停對外服務(wù),進(jìn)入新一輪Leader選舉,其過程和啟動時期的Leader選舉過程基本一致。假設(shè)正在運(yùn)行的有Server1、Server2、Server3三臺服務(wù)器,當(dāng)前Leader是Server2,若某一時刻Leader掛了,此時便開始Leader選舉。選舉過程如下
??(1)?變更狀態(tài)。Leader掛后,余下的非Observer服務(wù)器都會講自己的服務(wù)器狀態(tài)變更為LOOKING,然后開始進(jìn)入Leader選舉過程。
??(2)?每個Server會發(fā)出一個投票。在運(yùn)行期間,每個服務(wù)器上的ZXID可能不同,此時假定Server1的ZXID為123,Server3的ZXID為122;在第一輪投票中,Server1和Server3都會投自己,產(chǎn)生投票(1,?123),(3,?122),然后各自將投票發(fā)送給集群中所有機(jī)器。
??(3)?接收來自各個服務(wù)器的投票。與啟動時過程相同。
??(4)?處理投票。與啟動時過程相同,此時,Server1將會成為Leader。
??(5)?統(tǒng)計投票。與啟動時過程相同。
??(6)?改變服務(wù)器的狀態(tài)。與啟動時過程相同。
總結(jié):
在選舉時每個節(jié)點(diǎn)都有一個(myid,ZXID)表示;
對于初始化或leader宕機(jī)時,每個server發(fā)出一個投票給集群其他機(jī)器,所有請求掛起,開始選舉(如實(shí)例一);
超過半數(shù)的投票,就會成為Leader;
比較自己的選票和接收到的投票,優(yōu)先比較ZXID,再比較myid。如果大于自己,更換自己的選票并告訴其他server;
ZXID 是指當(dāng)前服務(wù)器數(shù)據(jù)越新,其成為Leader可能性越大。
myid 是指當(dāng)前server編號
8. 三大功能
為用戶提供數(shù)據(jù)的注冊和查詢服務(wù)
為用戶提供數(shù)據(jù)節(jié)點(diǎn)的監(jiān)聽注冊服務(wù)
跟用戶之間保持心跳通信以感知用戶的狀態(tài)
9. Java Api 操作zookeeper
分布式配置中心
jar引入
????????<dependency>
????????????<groupId>org.apache.zookeeper</groupId>
????????????<artifactId>zookeeper</artifactId>
????????????<version>3.6.1</version>
????????</dependency>
創(chuàng)建節(jié)點(diǎn)
[zk:?localhost:2181(CONNECTED)?6]?create?/username?javapub
Created?/username
啟動客戶端代碼
package?javapub;
/**
?*?@author?wangshiyu?rodert
?*?@date?2020/5/18?13:15
?*?@description
?*/
import?java.util.concurrent.CountDownLatch;
import?org.apache.zookeeper.WatchedEvent;
import?org.apache.zookeeper.Watcher;
import?org.apache.zookeeper.Watcher.Event.EventType;
import?org.apache.zookeeper.Watcher.Event.KeeperState;
import?org.apache.zookeeper.ZooKeeper;
import?org.apache.zookeeper.data.Stat;
/**
?*?分布式配置中心demo
?*?@author
?*
?*/
public?class?ZooKeeperProSync?implements?Watcher?{
????private?static?CountDownLatch?connectedSemaphore?=?new?CountDownLatch(1);
????private?static?ZooKeeper?zk?=?null;
????private?static?Stat?stat?=?new?Stat();
????public?static?void?main(String[]?args)?throws?Exception?{
????????//zookeeper配置數(shù)據(jù)存放路徑
????????String?path?=?"/username";
????????//連接zookeeper并且注冊一個默認(rèn)的監(jiān)聽器
????????zk?=?new?ZooKeeper("127.0.0.1:2181",?5000,?//
????????????????new?ZooKeeperProSync());
????????//等待zk連接成功的通知
????????connectedSemaphore.await();
????????//獲取path目錄節(jié)點(diǎn)的配置數(shù)據(jù),并注冊默認(rèn)的監(jiān)聽器
????????System.out.println(new?String(zk.getData(path,?true,?stat)));
????????Thread.sleep(Integer.MAX_VALUE);
????}
????public?void?process(WatchedEvent?event)?{
????????if?(KeeperState.SyncConnected?==?event.getState())?{??//zk連接成功通知事件
????????????if?(EventType.None?==?event.getType()?&&?null?==?event.getPath())?{
????????????????connectedSemaphore.countDown();
????????????}?else?if?(event.getType()?==?EventType.NodeDataChanged)?{??//zk目錄節(jié)點(diǎn)數(shù)據(jù)變化通知事件
????????????????try?{
????????????????????System.out.println("配置已修改,新值為:"?+?new?String(zk.getData(event.getPath(),?true,?stat)));
????????????????}?catch?(Exception?e)?{
????????????????}
????????????}
????????}
????}
}
啟動java客戶端代碼,正確讀取到 /username 目錄下數(shù)據(jù),javapub
修改 /username 下數(shù)據(jù)
[zk:?localhost:2181(CONNECTED)?7]?set?/username?javapub-rodert
cZxid?=?0x4
ctime?=?Mon?May?18?13:20:59?CST?2020
mZxid?=?0x6
mtime?=?Mon?May?18?13:27:54?CST?2020
pZxid?=?0x4
cversion?=?0
dataVersion?=?1
aclVersion?=?0
ephemeralOwner?=?0x0
dataLength?=?14
numChildren?=?0
客戶端同步如下: 配置已修改,新值為:javapub-rodert