...
一长窄、相關(guān)概念
- 中間件:為分布式系統(tǒng)提供協(xié)調(diào)服務(wù)的組件按厘,如專門用于計(jì)算服務(wù)的機(jī)器就是一個(gè)計(jì)算型中間件,還有專門用于存儲的機(jī)器就是存儲中間件等等碳默;
- 分布式系統(tǒng):把一套系統(tǒng)按照業(yè)務(wù)進(jìn)行橫向拆分贾陷,不同業(yè)務(wù)階段的處理放在不同的計(jì)算機(jī)上,由這些計(jì)算機(jī)組成了一套業(yè)務(wù)處理鏈嘱根,那么這個(gè)業(yè)務(wù)處理鏈就是一個(gè)分布式系統(tǒng)髓废;
- 內(nèi)部的每臺計(jì)算機(jī)都可以進(jìn)行通信(rpc/rest);
- 可以這樣理解:加入我們的系統(tǒng)目前就在一個(gè)項(xiàng)目中该抒,橫向拆分就是把MVC層拆出來作為一個(gè)模塊節(jié)點(diǎn)A慌洪,把Service層拆分出來當(dāng)做節(jié)點(diǎn)B,把DAO層拆分出來當(dāng)做節(jié)點(diǎn)C凑保,那么節(jié)點(diǎn)A可以部署在服務(wù)器a上冈爹,節(jié)點(diǎn)B部署在服務(wù)器b上,節(jié)點(diǎn)C部署在節(jié)點(diǎn)c上欧引;運(yùn)行的時(shí)候客戶端的請求發(fā)送到服務(wù)器a上频伤,a處理的時(shí)候使用rpc/rest調(diào)用b,b處理的時(shí)候調(diào)用c芝此,這就是所謂的分布式系統(tǒng)憋肖;而且這個(gè)系統(tǒng)的特點(diǎn)很明顯因痛,如果b的處理壓力過大,那么b節(jié)點(diǎn)可以單獨(dú)的擴(kuò)展為集群岸更,這樣這個(gè)系統(tǒng)就成為了一個(gè)分布式集群系統(tǒng)鸵膏,大大增加了系統(tǒng)的可擴(kuò)展性,以及系統(tǒng)運(yùn)行時(shí)候的性能的保障怎炊;
- ZooKeeper:一個(gè)基于觀察者模式設(shè)計(jì)的较性,為用戶的分布式應(yīng)用程序提供分布式的協(xié)調(diào)服務(wù)的中間件框架;簡稱ZK结胀;主要用來解決分布式集群中應(yīng)用系統(tǒng)的一致性問題赞咙;支持Java,并且提供java和c的相關(guān)API糟港;
- ZK的作用
- Leader節(jié)點(diǎn)選舉:就是一個(gè)主備切換的過程攀操,當(dāng)主節(jié)點(diǎn)掛了以后,從節(jié)點(diǎn)就可以接手主節(jié)點(diǎn)的工作秸抚,保證系統(tǒng)的高可用速和。
- 統(tǒng)一配置管理:只需要配置部署一臺服務(wù)器,就可以把這個(gè)服務(wù)器上的配置資源更新到其他的服務(wù)器上剥汤。常用于云計(jì)算部署颠放;
- 發(fā)布訂閱: 類似于MQ、Dubbo吭敢,發(fā)布者把數(shù)據(jù)發(fā)布到znode上碰凶,訂閱者會(huì)去獲取這個(gè)信息;
- 提供分布式鎖:在分布式環(huán)境中鹿驼,各個(gè)服務(wù)器可能會(huì)搶奪同一個(gè)資源欲低,分布式鎖就是解決這個(gè)搶奪過程的同步問題;
- 集群管理:集群中各個(gè)服務(wù)器之間保持?jǐn)?shù)據(jù)的強(qiáng)一致性畜晰;
- ZK中的節(jié)點(diǎn)角色分兩種:Leader和Follower/Observer砾莱,只有集群中有半數(shù)以上節(jié)點(diǎn)存活,集群就能提供服務(wù)凄鼻,一般情況下集群的節(jié)點(diǎn)數(shù)目不少于3個(gè)且為奇數(shù)個(gè)腊瑟;一個(gè)Leader,多個(gè)Follower組成一個(gè)ZK集群块蚌;
二闰非、ZooKeeper單機(jī)安裝配置
- 下載安裝
- 建議在官方歸檔頁面下載;
- 解壓下載到的安裝包匈子,配置環(huán)境變量
ZOOKEEPER_HOME
和PATH
河胎;
- ZooKeeper的配置
- 安裝完成后闯袒,在conf文件夾下面有一個(gè)文件:zoo_sample.cfg文件虎敦,拷貝這個(gè)文件為zoo.cfg游岳;
- 在這個(gè)配置文件中有很多的常用的變量需要我們配置:
- tickTime:這個(gè)時(shí)間是作為 Zookeeper 服務(wù)器之間或客戶端與服務(wù)器之間維持心跳的時(shí)間間隔,也就是每個(gè) tickTime 時(shí)間就會(huì)發(fā)送一個(gè)心跳其徙,單位:毫秒胚迫,單機(jī)集群通用配置;
- initLimit:初始化時(shí)唾那,F(xiàn)ollower節(jié)點(diǎn)連接到Leader節(jié)點(diǎn)的最長能忍受的心跳間隔數(shù)访锻,按照tickTime倍數(shù)形式表示,集群配置闹获;
- syncLimit:Follower節(jié)點(diǎn)和Leader節(jié)點(diǎn)之間發(fā)送請求與應(yīng)答最多能忍受的心跳間隔期犬,集群配置;
- dataDir:常用數(shù)據(jù)所在目錄避诽,必須配置龟虎,單機(jī)集群通用配置;
- dataLogDir:日志目錄沙庐,如果沒有配置鲤妥,則使用dataDir,單機(jī)集群通用配置拱雏;
- clientPort:連接服務(wù)器的端口棉安,默認(rèn)2181,單機(jī)集群通用配置铸抑;
- ZooKeeper的使用
- 啟動(dòng)ZooKeeper服務(wù):
./zkServer.sh start ./zkServer.sh start-foreground
- 關(guān)閉ZooKeeper服務(wù):
./zkServer.sh stop
- 重啟ZooKeeper服務(wù):
./zkServer.sh restart
- 查看ZooKeeper服務(wù)狀態(tài):
./zkServer.sh status
- 啟動(dòng)ZooKeeper服務(wù):
三贡耽、ZooKeeper的數(shù)據(jù)模型
- ZK的數(shù)據(jù)模型
- 是一個(gè)樹形模型;
- 每一個(gè)節(jié)點(diǎn)成為znode鹊汛,每個(gè)節(jié)點(diǎn)都存儲自身的數(shù)據(jù)菇爪,可以有子節(jié)點(diǎn);
- 每個(gè)節(jié)點(diǎn)都有兩種類型:臨時(shí)和永久柒昏;臨時(shí)節(jié)點(diǎn)在與客戶端斷開連接之后消失凳宙,子節(jié)點(diǎn)和自身數(shù)據(jù)都會(huì)丟失;
- 每個(gè)節(jié)點(diǎn)都有一個(gè)版本號职祷,該版本號會(huì)隨著該節(jié)點(diǎn)的信息更新而更新(類似數(shù)據(jù)庫的樂觀鎖)氏涩,這個(gè)版本信息可以通過命令來展示;
- 每個(gè)節(jié)點(diǎn)存儲的數(shù)據(jù)大小不宜過大(幾K)有梆;
- 每個(gè)節(jié)點(diǎn)都可以設(shè)置ACL權(quán)限是尖;
- ZK的數(shù)據(jù)模型的相關(guān)操作
- 連接客戶端,必須先啟動(dòng)Server泥耀,再去使用Client連接Server
./zkCli.sh
- zkCli相關(guān)命令
- 查看zkCli的所有命令
help
- 列舉當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)
ls <node-path> [watch]
- 查看當(dāng)前節(jié)點(diǎn)的狀態(tài)
stat <node-path>
- cZxid:節(jié)點(diǎn)id饺汹;
- cTime:create time;
- mZXid:修改節(jié)點(diǎn)后的節(jié)點(diǎn)id痰催;
- mTime:modify time兜辞;
- pZxid:子節(jié)點(diǎn)的id半抱?麦撵??
- cversion:子節(jié)點(diǎn)的version;
- dataVersion:當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)版本跺讯;
- aclVersion:acl權(quán)限版本铛只;
- ephemeralOwner:臨時(shí)Owner內(nèi)存地址征炼,如果值是
0x0
摩泪,那么這個(gè)節(jié)點(diǎn)就是持久性節(jié)點(diǎn),否則這個(gè)節(jié)點(diǎn)就是臨時(shí)節(jié)點(diǎn)韩脑;臨時(shí)節(jié)點(diǎn)不是一斷開就立即刪除(心跳機(jī)制氢妈,斷開連接之后,沒有了心跳段多,就會(huì)去刪除臨時(shí)節(jié)點(diǎn))允懂,而是有一個(gè)實(shí)效時(shí)間,在這個(gè)時(shí)間過后才會(huì)刪除衩匣; - dataLength:數(shù)據(jù)字節(jié)數(shù)蕾总;
- numChildren:子節(jié)點(diǎn)個(gè)數(shù);
- 同事查看當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)以及當(dāng)前節(jié)點(diǎn)的狀態(tài)
ls2 <node-path> [watch] # 本質(zhì)上就是ls和stat組合命令
- 查看當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)
get <node-path>
- 查看zkCli的所有命令
- 關(guān)閉客戶端連接
- Ctrl + C
- 連接客戶端,必須先啟動(dòng)Server泥耀,再去使用Client連接Server
四琅捏、ZK的常用操作
- Session基本原理
- 服務(wù)端和客戶端之間的連接稱之為Session生百;
- 每個(gè)Session都可以設(shè)置一個(gè)超市時(shí)間;
- 心跳結(jié)束柄延,Session過期蚀浆,臨時(shí)節(jié)點(diǎn)就會(huì)被丟棄;
- 心跳機(jī)制:客戶端向服務(wù)端的ping包請求搜吧;
- 常用命令
- 創(chuàng)建節(jié)點(diǎn)
create [-s] [-e] <node-path> <node-data> <acl>
-
-s
:sequence市俊,創(chuàng)建順序節(jié)點(diǎn) -
-e
:ephemeral,創(chuàng)建臨時(shí)節(jié)點(diǎn)滤奈;臨時(shí)節(jié)點(diǎn)上不可以創(chuàng)建永久節(jié)點(diǎn)摆昧;
-
- 修改節(jié)點(diǎn)
set <node-path> <node-data> [<version>]
- 刪除節(jié)點(diǎn)
delete <node-path> [<version>]
- 創(chuàng)建節(jié)點(diǎn)
五、watch機(jī)制
- 機(jī)制簡介
- 我們對每個(gè)節(jié)點(diǎn)的操作都會(huì)有一個(gè)監(jiān)聽者watch蜒程,可以理解為一個(gè)觸發(fā)器绅你;當(dāng)有人操作了節(jié)點(diǎn),那么就會(huì)觸發(fā)這個(gè)節(jié)點(diǎn)的watch事件昭躺,來更新這個(gè)節(jié)點(diǎn)以及父節(jié)點(diǎn)的屬性值忌锯;
- watch是一次性的,觸發(fā)后事件完成后就立即銷毀领炫;可以通過Apache實(shí)現(xiàn)永久性的watch偶垮;
- 不同的類型的watch觸發(fā)的事件也是不同的:
- 節(jié)點(diǎn)創(chuàng)建
- 節(jié)點(diǎn)刪除
- 節(jié)點(diǎn)數(shù)據(jù)更新
- 常見的watch事件
- 當(dāng)前節(jié)點(diǎn)創(chuàng)建節(jié)點(diǎn)觸發(fā)事件:NodeCreated
- 如果當(dāng)前節(jié)點(diǎn)不存在也可以設(shè)置,報(bào)錯(cuò)可忽略
- 當(dāng)前節(jié)點(diǎn)數(shù)據(jù)更新事件:NodeDataChanged
- 當(dāng)前節(jié)點(diǎn)刪除事件:NodeDeleted
- 子節(jié)點(diǎn)創(chuàng)建、刪除事件:NodeChildrenChanged
- 修改子節(jié)點(diǎn)似舵,不觸發(fā)父節(jié)點(diǎn)事件
- 當(dāng)前節(jié)點(diǎn)創(chuàng)建節(jié)點(diǎn)觸發(fā)事件:NodeCreated
- watch相關(guān)的命令
- 通過查詢數(shù)據(jù)設(shè)置watch
get <node-path> [watch]
- 通過查詢節(jié)點(diǎn)狀態(tài)設(shè)置watch
stat <node-path> [watch]
- 通過列舉子節(jié)點(diǎn)設(shè)置watch
ls[2] <node-path> [watch]
- 通過查詢數(shù)據(jù)設(shè)置watch
- Watch使用場景
- 集群統(tǒng)一資源配置
六脚猾、權(quán)限控制列表
- ACL簡介
- 權(quán)限控制列表:ACL(Access Control List)
- 為了保障數(shù)據(jù)的安全性,針對節(jié)點(diǎn)設(shè)置相關(guān)的讀寫權(quán)限啄枕;這個(gè)權(quán)限可以設(shè)置不同的權(quán)限范圍和角色婚陪;
- ACL的結(jié)構(gòu)單位:
[<scheme>:<id>:<permissions>]
:- scheme:采用的權(quán)限機(jī)制
- world:這個(gè)scheme下只有一個(gè)id族沃,即anyone频祝;組合
world:anyone:<permissions>
; - auth:認(rèn)證登錄脆淹;組合:
auth:<username>:<password>:<permissions>
常空; - digest:加密訪問;組合
digest:<username>:BASE64(SHA1(<password>)):<persissions>
盖溺; - ip:限制ip訪問漓糙;組合
ip:<ip-addr>:<permissions>
; - super:超級管理員權(quán)限烘嘱;
- 編輯zkServer.sh中昆禽,找到行nohup行的${ZOO_LOG4J_PROP}",在其后添加
"-Dzookeeper.DigestAuthenticationProvider.superDigest=<username>:<BASE64(SHA1(<password>))>"
- 重啟zkServer蝇庭;
- 登陸認(rèn)證:
addauth digest <username>:<BASE64(SHA1(<password>))>
醉鳖,這樣就可以使用超級管理員賬號了;
- 編輯zkServer.sh中昆禽,找到行nohup行的${ZOO_LOG4J_PROP}",在其后添加
- world:這個(gè)scheme下只有一個(gè)id族沃,即anyone频祝;組合
- id:允許訪問的用戶id
- permissions:權(quán)限字符串:CRDWA哮内,這5個(gè)字母可以任意組合
- Create:創(chuàng)建權(quán)限
- Read:讀當(dāng)前節(jié)點(diǎn)和子節(jié)點(diǎn)的權(quán)限
- Delete:刪除權(quán)限
- Write:寫權(quán)限
- Admin:分配權(quán)限的權(quán)限
- scheme:采用的權(quán)限機(jī)制
- ACL使用
- addauth:添加認(rèn)證授權(quán)信息:
addauth <scheme> <acl> # 添加一個(gè)用戶名為zhangsan盗棵,密碼為123456的用戶:認(rèn)證過程 addauth digest zhangsan:123456
- setAcl:設(shè)置具體節(jié)點(diǎn)的權(quán)限信息:
setAcl <node-path> <acl> # 給一個(gè)用戶zhangsan設(shè)置權(quán)限,注意:只是第一次設(shè)置有效 setAcl /level1/level2 auth:zhangsan:123456:cdrwa
- getAcl:獲取具體節(jié)點(diǎn)的權(quán)限信息:
getAcl <node-path> # 默認(rèn)權(quán)限: 'world,' anyone : cdrwa
- addauth:添加認(rèn)證授權(quán)信息:
- ACL使用場景
- 分離開發(fā)測試環(huán)境:即不同的節(jié)點(diǎn)設(shè)置不同的訪問權(quán)限北发;
七纹因、四字命令(The Four Letter Words)
- 簡介
- ZK可以通過四字命令與服務(wù)器進(jìn)行交互;比較適合監(jiān)控信息琳拨;
- 使用
- 安裝nc:
yum install nc
- nc命令使用規(guī)則:
echo [command] | nc [ip] [port]
- 安裝nc:
- 常用命令
- stat:查看ZK的狀態(tài)信息瞭恰;
echo stat | nc localhost 2181 # 執(zhí)行結(jié)果: Zookeeper version: 3.4.9-1757313, built on 08/23/2016 06:50 GMT Clients: /localhost:46026[0](queued=0,recved=1,sent=0) Latency min/avg/max: 0/1/8 Received: 10 Sent: 9 Connections: 1 Outstanding: 0 Zxid: 0xa Mode: standalone Node count: 4
- ruok:查看ZK的啟動(dòng)狀態(tài);
echo ruok | nc localhost 2181 # 執(zhí)行結(jié)果: imok
- dump:查看ZK的啟動(dòng)狀態(tài)狱庇;
echo dump | nc localhost 2181 # 執(zhí)行結(jié)果: SessionTracker dump: Session Sets (0): ephemeral nodes dump: Sessions with Ephemerals (0):
- conf:查看ZK服務(wù)器的相關(guān)配置參數(shù)寄疏;
echo conf | nc localhost 2181 # 執(zhí)行結(jié)果: clientPort=2181 dataDir=/usr/local/bin/zookeeper-3.4.9/data/version-2 dataLogDir=/usr/local/bin/zookeeper-3.4.9/log/version-2 tickTime=2000 maxClientCnxns=60 minSessionTimeout=4000 maxSessionTimeout=40000 serverId=0
- cons:查看連接到ZK服務(wù)器的信息;
echo cons | nc localhost 2181 # 執(zhí)行結(jié)果: /0:0:0:0:0:0:0:1:49998[0](queued=0,recved=1,sent=0) /0:0:0:0:0:0:0:1:49996[1](queued=0,recved=1,sent=1,sid=0x1645e34b63f0001,lop=SESS,est=1530597540388,to=30000,lcxid=0x0,lzxid=0xb,lresp=1530597540403,llat=14,minlat=0,avglat=14,maxlat=14)
- envi:查看ZK服務(wù)器的環(huán)境變量僵井;
echo envi | nc localhost 2181 # 執(zhí)行結(jié)果: Environment: zookeeper.version=3.4.9-1757313, built on 08/23/2016 06:50 GMT host.name=localhost java.version=1.8.0_171 java.vendor=Oracle Corporation java.home=/usr/local/bin/jdk1.8.0_171/jre java.class.path=/usr/local/bin/zookeeper-3.4.9/bin/../build/classes:/usr/local/bin/zookeeper-3.4.9/bin/../build/lib/*.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/slf4j-log4j12-1.6.1.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/slf4j-api-1.6.1.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/netty-3.10.5.Final.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/log4j-1.2.16.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/jline-0.9.94.jar:/usr/local/bin/zookeeper-3.4.9/bin/../zookeeper-3.4.9.jar:/usr/local/bin/zookeeper-3.4.9/bin/../src/java/lib/*.jar:/usr/local/bin/zookeeper-3.4.9/bin/../conf: java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib java.io.tmpdir=/tmp java.compiler=<NA> os.name=Linux os.arch=amd64 os.version=3.10.0-862.3.2.el7.x86_64 user.name=root user.home=/root user.dir=/usr/local/bin/zookeeper-3.4.9/bin
- mntr:查看ZK服務(wù)器的健康信息陕截;
echo mntr | nc localhost 2181 # 執(zhí)行結(jié)果: zk_version 3.4.9-1757313, built on 08/23/2016 06:50 GMT zk_avg_latency 1 zk_max_latency 14 zk_min_latency 0 zk_packets_received 59 zk_packets_sent 58 zk_num_alive_connections 2 zk_outstanding_requests 0 zk_server_state standalone zk_znode_count 4 zk_watch_count 0 zk_ephemerals_count 0 zk_approximate_data_size 27 zk_open_file_descriptor_count 27 zk_max_file_descriptor_count 4096
- wchs:查看ZK服務(wù)器的watch信息;
echo wchs | nc localhost 2181 # 執(zhí)行結(jié)果: 0 connections watching 0 paths Total watches:0
- wchc:查看ZK服務(wù)器中session與帶有watch的節(jié)點(diǎn)的關(guān)系信息(3.4.10后需要開啟白名單才能執(zhí)行)批什;
echo wchc | nc localhost 2181
- wchp:查看ZK服務(wù)器中帶有watch的節(jié)點(diǎn)在哪個(gè)session中(3.4.10后需要開啟白名單才能執(zhí)行)农曲;
echo wchp | nc localhost 2181
- 開啟白名單:在zoo.cfg文件中添加如下一行命令,重啟即可:
4lw.commands.whitelist=* 4lw.commands.whitelist=<cmd1>,<cmd2>...
- stat:查看ZK的狀態(tài)信息瞭恰;
八、ZK搭建集群
-
偽分布式環(huán)境搭建(為學(xué)習(xí))
- 下載ZooKeeper安裝包乳规;
- 解壓安裝包形葬;
- 復(fù)制conf文件夾下的zoo_sample.cfg為zoo.cfg;
- 配置各個(gè)節(jié)點(diǎn)的myid:向每個(gè)節(jié)點(diǎn)的ZK的數(shù)據(jù)文件夾中寫入一個(gè)文件
myid
暮的,這個(gè)文件中寫入當(dāng)前機(jī)器的id編號即可笙以; - 編輯zoo.cfg:
- 配置
dataDir
和dataLogDir
,不要放置在tmp下冻辩,個(gè)人喜歡放在安裝目錄或者獨(dú)立的硬盤的一個(gè)目錄下猖腕,并且確保程序有訪問權(quán)限; - 在每個(gè)節(jié)點(diǎn)上配置所有節(jié)點(diǎn)的端口映射關(guān)系:①對外訪問端口:2181恨闪;②Leader和Follower之間的端口:2888倘感;③組件之間投票通訊端口:3888;對應(yīng)的配置如下:
<server-name-prefix>.<myid>=<host>:<leader-listener-port>:<vote-port>
- <leader-listener-port>:是該服務(wù)器一旦成為Leader之后需要監(jiān)聽的端口咙咽,用于接收來自follower的請求老玛;
- <vote-port>:集群中的每一個(gè)節(jié)點(diǎn)在最開始選舉Leader時(shí)監(jiān)聽的端口,用于服務(wù)器互相之間通信選舉Leader钧敞;
- 配置
- 建議關(guān)閉防火墻蜡豹,因?yàn)榧褐辉趦?nèi)部訪問,不會(huì)暴露到對外的環(huán)境中溉苛;
真分布式環(huán)境搭建(為應(yīng)用)
九镜廉、ZK集群的特點(diǎn)
- 數(shù)據(jù)一致性:每個(gè)ZK節(jié)點(diǎn)保存一份相同的數(shù)據(jù)副本,client無論連接到哪個(gè)節(jié)點(diǎn)炊昆,數(shù)據(jù)都是一致的桨吊,即
單一視圖
; - 分布式讀寫凤巨,更新請求轉(zhuǎn)發(fā)视乐,由leader實(shí)施;更新請求順序進(jìn)行敢茁,來自同一個(gè)client的更新請求按其發(fā)送順序依次執(zhí)行佑淀;
- 操作原子性:一損俱損,全榮為榮彰檬;
- 數(shù)據(jù)實(shí)時(shí)性:在一定時(shí)間范圍內(nèi)伸刃,client能讀到最新數(shù)據(jù),ZK節(jié)點(diǎn)越多實(shí)時(shí)性越差逢倍;
- 數(shù)據(jù)可靠性:每次對ZK的操作都記錄在服務(wù)端捧颅;
十、ZK選舉Leader
- 問題的提出:
- 在配置集群的時(shí)候我么是不配置哪個(gè)節(jié)點(diǎn)是Leader節(jié)點(diǎn)较雕,哪些事Follower節(jié)點(diǎn)的碉哑,當(dāng)我們在每個(gè)節(jié)點(diǎn)上配置了這個(gè)集群的所有節(jié)點(diǎn)的映射關(guān)系之后挚币,當(dāng)ZK節(jié)點(diǎn)一個(gè)接著一個(gè)的啟動(dòng),系統(tǒng)會(huì)自動(dòng)的選舉出Leader扣典,那這個(gè)Leader的節(jié)點(diǎn)選舉時(shí)怎樣進(jìn)行的呢妆毕?
- Leader選舉機(jī)制
- Leader選舉機(jī)制的考慮因素:
- 節(jié)點(diǎn)id:id即權(quán)重,節(jié)點(diǎn)id越大選舉的權(quán)重越大贮尖;
- 數(shù)據(jù)id:數(shù)據(jù)越新笛粘,數(shù)據(jù)id越大,選舉的權(quán)重越大湿硝;
- 投票次數(shù):得票者越大薪前,越容易成為Leader;
- 選舉中節(jié)點(diǎn)的狀態(tài):
- LOOKING:競選狀態(tài)图柏;
- FOLLOWING:隨從狀態(tài)序六,同步leader狀態(tài)任连,參與投票蚤吹;
- OBSERVING:觀察狀態(tài),同步leader狀態(tài)随抠,不參與投票裁着;
- LEADING:領(lǐng)導(dǎo)者狀態(tài);
- Leader選舉機(jī)制的考慮因素:
十一拱她、ZK的JavaAPI的使用
- 添加ZooKeeper的pom:
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.5</version> </dependency>
-
org.apache.zookeeper.Zookeeper
是客戶端入口主類二驰,負(fù)責(zé)建立與ZK服務(wù)器節(jié)點(diǎn)的會(huì)話;主要的API參考下面的測試類秉沼;
-
- ZKClientTest:
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import org.junit.Before; import org.junit.Test; import java.util.List; /** * @Author: ShrekerNil * @Date: 2018/7/5 10:28 * @Description: ZooKeeper的API的使用 */ @Slf4j public class ZKClientTest { private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181"; private static final int sessionTimeout = 2000; private ZooKeeper zkClient = null; @Before public void init() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { // Watcher接口是當(dāng)zkClient監(jiān)聽到watch事件觸發(fā)的時(shí)候就會(huì)去調(diào)用該接口process方法的一個(gè)接口 public void process(WatchedEvent event) { // 收到事件通知后的回調(diào)函數(shù)(應(yīng)該是我們自己的事件處理邏輯) log.info("接收到事件桶雀,類型:{},事件發(fā)生節(jié)點(diǎn):{}", event.getType(), event.getPath()); // 因?yàn)楸O(jiān)聽器只會(huì)生效一次唬复,所以在消費(fèi)監(jiān)聽器之后再次注冊監(jiān)聽器 try { zkClient.getChildren("/", true); } catch (Exception e) { log.error("添加監(jiān)聽器失敗", e); } } }); } // 創(chuàng)建數(shù)據(jù)節(jié)點(diǎn)到ZK中 @Test public void testCreate() throws Exception { // 參數(shù)1:要?jiǎng)?chuàng)建的節(jié)點(diǎn)的路徑 參數(shù)2:節(jié)點(diǎn)大數(shù)據(jù) 參數(shù)3:節(jié)點(diǎn)的權(quán)限 參數(shù)4:節(jié)點(diǎn)的類型 String nodePathCreated = zkClient.create("/node110", "hellozk".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //上傳的數(shù)據(jù)可以是任何類型矗积,但都要轉(zhuǎn)成byte[] log.info(nodePathCreated); } //判斷znode是否存在 @Test public void testExist() throws Exception { Stat stat = zkClient.exists("/node110", false); log.info(stat == null ? "Not exist!" : "Exists"); } // 獲取子節(jié)點(diǎn) @Test public void getChildren() throws Exception { List<String> children = zkClient.getChildren("/", true); for (String child : children) { log.info(child); } Thread.sleep(20000); } //獲取znode的數(shù)據(jù) @Test public void getNodeData() throws Exception { byte[] data = zkClient.getData("/node110", false, null); log.info(new String(data)); } //設(shè)置znode數(shù)據(jù) @Test public void setNodeData() throws Exception { zkClient.setData("/node110", "something in my heart is ...".getBytes(), -1); byte[] data = zkClient.getData("/node110", false, null); log.info(new String(data)); } //刪除znode @Test public void deleteZnode() throws Exception { //第二個(gè)參數(shù):指定要?jiǎng)h除的版本,-1表示刪除所有版本 zkClient.delete("/node110", -1); } }
十二敞咧、ZK的使用場景(參考自這里)
- 統(tǒng)一命名服務(wù)(Name Service)
- 統(tǒng)一命名服務(wù)是ZK的內(nèi)置功能棘捣,類似于JNDI,我們在調(diào)用其API的時(shí)候使用了這一功能休建,在這里不再贅述乍恐;
- 配置管理(Configuration Management)
- 問題:配置的管理在分布式應(yīng)用環(huán)境中很常見,例如同一個(gè)應(yīng)用系統(tǒng)需要多臺服務(wù)器配合運(yùn)行测砂,但是它們運(yùn)行的應(yīng)用系統(tǒng)的某些配置項(xiàng)是相同的茵烈,如果要修改這些相同的配置項(xiàng),那么就必須同時(shí)修改每臺運(yùn)行這個(gè)應(yīng)用系統(tǒng)的服務(wù)器砌些,這樣非常麻煩而且容易出錯(cuò)呜投。
- 解決:ZK就能很好的幫我們完成這項(xiàng)配置工作,可以把配置存儲在ZK的一個(gè)節(jié)點(diǎn)上,設(shè)置監(jiān)聽宙彪,當(dāng)我們修改了ZK中這個(gè)節(jié)點(diǎn)的數(shù)據(jù)的時(shí)候矩动,監(jiān)聽器就能偵測到這個(gè)事件,進(jìn)而把配置應(yīng)用到各自的服務(wù)器中释漆。
- 集群管理(Group Membership)
- 問題:在做集群的時(shí)候悲没,一般我們都使用Master、Slave模式男图,但是如果Master掛了呢示姿?這個(gè)時(shí)候就會(huì)出現(xiàn)問題,整個(gè)服務(wù)就掛掉了逊笆,HA化為泡影栈戳;
- 解決:這個(gè)問題的解決方式有很多,很多的框架都提供了集群管理的功能难裆,當(dāng)然我們今天的主角ZK也不例外子檀。首先我們應(yīng)該注冊監(jiān)聽一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),客戶端在服務(wù)器上注冊臨時(shí)(EPHEMERAL)的節(jié)點(diǎn)乃戈,當(dāng)出現(xiàn)如上的問題的時(shí)候褂痰,就觸發(fā)ZK的事件,我們就知道哪個(gè)節(jié)點(diǎn)掛了症虑,根據(jù)一定的算法選舉出新的Leader缩歪,讓集群接著向外界提供服務(wù);
- 共享鎖(Locks)
- 共享鎖在同一個(gè)進(jìn)程中很容易實(shí)現(xiàn)谍憔,但是在跨進(jìn)程或者在不同 Server 之間就不好實(shí)現(xiàn)了匪蝙。Zookeeper 卻很容易實(shí)現(xiàn)這個(gè)功能,實(shí)現(xiàn)方式也是需要獲得鎖的 Server 創(chuàng)建一個(gè) EPHEMERAL_SEQUENTIAL 目錄節(jié)點(diǎn)习贫,然后調(diào)用 getChildren方法獲取當(dāng)前的目錄節(jié)點(diǎn)列表中最小的目錄節(jié)點(diǎn)是不是就是自己創(chuàng)建的目錄節(jié)點(diǎn)逛球,如果正是自己創(chuàng)建的,那么它就獲得了這個(gè)鎖沈条,如果不是那么它就調(diào)用 exists(String path, boolean watch) 方法并監(jiān)控 Zookeeper 上目錄節(jié)點(diǎn)列表的變化需忿,一直到自己創(chuàng)建的節(jié)點(diǎn)是列表中最小編號的目錄節(jié)點(diǎn),從而獲得鎖蜡歹,釋放鎖很簡單屋厘,只要?jiǎng)h除前面它自己所創(chuàng)建的目錄節(jié)點(diǎn)就行了。
- 隊(duì)列管理
- ZK可以處理兩種類型的隊(duì)列:同步隊(duì)列和FIFO隊(duì)列(完整代碼參看這里)月而;
- 同步隊(duì)列實(shí)現(xiàn):當(dāng)一個(gè)隊(duì)列的成員都聚齊時(shí)汗洒,這個(gè)隊(duì)列才可用,否則一直等待所有成員到達(dá)父款;
- 實(shí)現(xiàn)思路:創(chuàng)建一個(gè)父目錄
/synchronizing
溢谤,每個(gè)成員都監(jiān)控標(biāo)志(Set Watch)位目錄/synchronizing/start
是否存在瞻凤,然后每個(gè)成員都加入這個(gè)隊(duì)列,加入隊(duì)列的方式就是創(chuàng)建/synchronizing/member_i
的臨時(shí)目錄節(jié)點(diǎn)世杀,然后每個(gè)成員獲取/ synchronizing
目錄的所有目錄節(jié)點(diǎn)阀参,也就是member_i
。判斷i
的值是否已經(jīng)是成員的個(gè)數(shù)瞻坝,如果小于成員個(gè)數(shù)等待/synchronizing/start
的出現(xiàn)蛛壳,如果已經(jīng)相等就創(chuàng)建/synchronizing/start
;
- 實(shí)現(xiàn)思路:創(chuàng)建一個(gè)父目錄
- FIFO隊(duì)列實(shí)現(xiàn):隊(duì)列按照 FIFO 方式進(jìn)行入隊(duì)和出隊(duì)操作所刀,例如實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者模型衙荐;
- 實(shí)現(xiàn)思路:實(shí)現(xiàn)的思路也非常簡單,就是在特定的目錄下創(chuàng)建
SEQUENTIAL
類型的子目錄 /queue_i浮创,這樣就能保證所有成員加入隊(duì)列時(shí)都是有編號的忧吟,出隊(duì)列時(shí)通過getChildren()
方法可以返回當(dāng)前所有的隊(duì)列中的元素,然后消費(fèi)其中最小的一個(gè)斩披,這樣就能保證 FIFO溜族;
- 實(shí)現(xiàn)思路:實(shí)現(xiàn)的思路也非常簡單,就是在特定的目錄下創(chuàng)建
十三、集群節(jié)點(diǎn)上下線感知系統(tǒng)實(shí)現(xiàn)
-
客戶端實(shí)現(xiàn)
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.util.ArrayList; import java.util.List; /** * @Author: ShrekerNil * @Date: 2018/7/5 14:21 * @Description: DistributedClient用來描述ZK在客戶端的用法 */ @Slf4j public class DistributedClient { private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181"; private static final int sessionTimeout = 2000; private static final String baseNode = "/servers"; private ZooKeeper zkClient = null; private volatile List<String> servers; /** * 創(chuàng)建到zk的客戶端連接 */ private void connectZoooKepper() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { public void process(WatchedEvent event) { try { updateServersAndListenChildrenNodes(); } catch (Exception e) { log.error("更新服務(wù)器列表失敗", e); } } }); } /** * 更新服務(wù)器列表雏掠,并且注冊子節(jié)點(diǎn)監(jiān)聽 */ private void updateServersAndListenChildrenNodes() throws Exception { // 獲取服務(wù)器子節(jié)點(diǎn)信息斩祭,并且對父節(jié)點(diǎn)進(jìn)行監(jiān)聽 List<String> nodes = zkClient.getChildren(baseNode, true); // 先創(chuàng)建一個(gè)局部的list來存服務(wù)器信息 List<String> temp = new ArrayList<String>(); for (String node : nodes) { // child只是子節(jié)點(diǎn)的節(jié)點(diǎn)名 byte[] data = zkClient.getData(baseNode + "/" + node, false, null); temp.add(new String(data)); } servers = temp; //打印服務(wù)器列表 log.debug("servers:{}", servers.toString()); } /** * 業(yè)務(wù)功能 */ private void handleBussiness() throws InterruptedException { log.debug("Client start working....."); log.debug("Using servers:{}", servers); Thread.sleep(Long.MAX_VALUE); } public static void main(String[] args) throws Exception { // 創(chuàng)建客戶端對象 DistributedClient client = new DistributedClient(); // 連接ZK client.connectZoooKepper(); // 更新servers的子節(jié)點(diǎn)信息(并監(jiān)聽)劣像,從中獲取服務(wù)器信息列表 client.updateServersAndListenChildrenNodes(); // 業(yè)務(wù)線程啟動(dòng) client.handleBussiness(); } }
-
集群端實(shí)現(xiàn):
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.*; /** * @Author: ShrekerNil * @Date: 2018/7/5 14:19 * @Description: DistributedServer用來描述ZK在服務(wù)器端的用法 */ @Slf4j public class DistributedServer { private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181"; private static final int sessionTimeout = 2000; private static final String parentNode = "/servers"; private ZooKeeper zkClient = null; /** * 創(chuàng)建到zk的客戶端連接 */ private void connectZoooKepper() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { public void process(WatchedEvent event) { // 收到事件通知后的回調(diào)函數(shù)(應(yīng)該是我們自己的事件處理邏輯) log.debug(event.getType() + "---" + event.getPath()); try { zkClient.getChildren("/", true); } catch (Exception e) { log.error("添加監(jiān)聽器失敗", e); } } }); } /** * 向zk集群注冊服務(wù)器信息 */ private void registerServer(String hostname) throws Exception { String newNode = zkClient.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); log.debug("Created new node {} in {}", newNode, hostname); } /** * 業(yè)務(wù)功能 */ private void handleBussiness(String hostname) throws InterruptedException { log.debug(hostname + " start working....."); Thread.sleep(Long.MAX_VALUE); } public static void main(String[] args) throws Exception { // 創(chuàng)建Server DistributedServer server = new DistributedServer(); // 連接ZK server.connectZoooKepper(); int length = args.length; if (length < 1) { log.error("請?zhí)砑訁?shù)hostname"); return; } // 利用zk連接注冊服務(wù)器信息 server.registerServer(args[0]); // 啟動(dòng)業(yè)務(wù)功能 server.handleBussiness(args[0]); } }
十四乡话、寫在最后
- 在本篇文章中提到了很多知識點(diǎn),但是很多知識點(diǎn)只是提了一下耳奕,由于篇幅有限绑青,只能寫到這里了,如果需要再補(bǔ)充吧
- 補(bǔ)充一個(gè)原理講解比較深刻的博客屋群,大家可以去看看闸婴;
- 文章中也引用了一些其他博主的內(nèi)容,已標(biāo)記出處芍躏,如果轉(zhuǎn)載請標(biāo)明這些博主的出處邪乍,我的就無所謂了,謝謝