1.介紹
1.1概念
zookeeper作用:用於維護配置資訊、命名、提供分散式同步和提供組服務
zookeeper主要是檔案系統和通知機制
檔案系統主要是用來儲存資料
通知機制主要是伺服器或者客戶端進行通知,並且監督
基於觀察者模式設計的分散式服務管理框架,開源的分散式框架
1.2特點
1):一個leader,多個follower的叢集
2):叢集只要有半數以上包括半數就可正常服務,一般安裝奇數臺伺服器
3):全域性資料一致,每個伺服器都儲存同樣的資料,實時更新
4):更新的請求順序保持順序(來自同一個伺服器)
5):資料更新的原子性,資料要麼成功要麼失敗(大事務)
6):資料實時更新性很快(因為資料量很小)
1.3主要的叢集步驟
1):服務端啟動時去註冊資訊(建立都是臨時節點)
2):獲取到當前線上伺服器列表,並且註冊監聽
3):伺服器節點下線
4):伺服器節點上下線事件通知
5):process(){重新再去獲取伺服器列表,並註冊監聽}
1.4資料結構
與 Unix 檔案系統很類似,可看成樹形結構,每個節點稱做一個ZNode。每一個ZNode預設能夠儲存 1MB 的資料。也就是隻能儲存小資料(一般配置資訊,註冊資訊等)
1.5應用場景
1):統一命名服務(域名服務)
在分散式環境下,經常需要對應用/服務進行統一命名,便於識別,eg:ip不容易記住,而域名容易記住
2):統一配置管理(一個叢集中的所有配置都一致,且也要實時更新同步)
3):將配置資訊寫入ZooKeeper上的一個Znode,各個客戶端伺服器監聽這個Znode。一旦Znode中的資料被修改,ZooKeeper將通知各個客戶端伺服器
4):統一叢集管理(掌握實時狀態)
5):將節點資訊寫入ZooKeeper上的一個ZNode。監聽ZNode獲取實時狀態變化
6):伺服器節點動態上下線
7):軟負載均衡(根據每個節點的訪問數,讓訪問數最少的伺服器處理最新的資料需求)
2.本地安裝
2.1安裝jdk
1.檢視是否已安裝JDK
yum list installed |grep java
2.解除安裝CentOS系統Java環境
# yum -y remove java-1.8.0-openjdk* *表示解除安裝所有openjdk相關檔案輸入
# yum -y remove tzdata-java.noarch 解除安裝tzdata-java
3.檢視JDK軟體包版本
# yum -y list java* 或者使用# yum searchjava | grep -i --color JDK
4.安裝
yum install java-1.8.0-openjdk* //安裝java1.8.0所有程式
java -version //檢視java版本資訊
注意:使用yum安裝環境變數自動就配好了
2.2下載安裝
官網:https://zookeeper.apache.org/
下載3.5.7穩定版本
下載:https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/apache-zookeeper-3.5.7-bin.tar.gz
解壓:tar -xf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
改名:mv apache-zookeeper-3.5.7-bin zookeeper-3.5.7
配置檔案改名:mv zoo_sample.cfg zoo.cfg
bin目錄 框架啟動停止,客戶端和服務端的
conf 配置檔案資訊
docs文件
lib 配置文件的依賴
2.3配置檔案修改
配置檔案:/opt/module/zookeeper-3.5.7/conf/zoo.cfg
修改:
dataDir = /opt/module/zookeeper-3.5.7/zkData // 通常修改的路徑
2.4啟動服務端
cd /opt/module/zookeeper-3.5.7/bin
[root@sg-15 bin]# ./zkServer.sh start // 啟動服務端
//檢視程式
[root@sg-15 bin]# jps -l
21172 sun.tools.jps.Jps
21110 org.apache.zookeeper.server.quorum.QuorumPeerMain
2.5啟動客戶端
[root@sg-15 bin]# ./zkCli.sh // 啟動客戶端
[zk: localhost:2181(CONNECTED) 4] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 5] quit //退出客戶端
[root@sg-15 bin]# ./zkServer.sh status // 檢視zookeeper狀態
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
2.6zookeeper常用命令
[root@sg-15 bin]# ./zkServer.sh start // 啟動服務端
[root@sg-15 bin]# ./zkServer.sh stop // 停止服務端
[root@sg-15 bin]# jps -l // 檢視程式
[root@sg-15 bin]# ./zkCli.sh // 啟動客戶端
[zk: localhost:2181(CONNECTED) 5] quit // 退出客戶端
[root@sg-15 bin]# ./zkServer.sh status // 檢視zookeeper狀態
2.7配置檔案解讀
配置檔案的5大引數:
tickTime = 2000 //通訊心跳時間,Zookeeper伺服器與客戶端心跳時間,單位毫秒
initLimit = 10 //Leader和Follower初始連線時能容忍的最多心跳數(tickTime的數量)
syncLimit = 5 //Leader和Follower之間通訊時間如果超過5個心跳時間,Leader認為Follwer死掉,從伺服器列表中刪除Follwer。
dataDir =/tmp/zookeeper //儲存zookeeper的資料,這是預設值,會定時被系統清除
dataDir儲存zookeeper的資料,預設是temp會被系統定期清除,通常改為自己的路徑
dataDir = /opt/module/zookeeper-3.5.7/zkData // 通常修改的路徑
clientPort = 2181 //客戶端的連線埠,一般不需要修改
3.叢集安裝
3.1叢集規劃
sg15,sg16,sg17三臺機器部署Zookeeper
3.2安裝
解壓:tar -xf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
改名:mv apache-zookeeper-3.5.7-bin zookeeper-3.5.7
配置檔案改名:mv zoo_sample.cfg zoo.cfg
3.3配置
[root@sg-15 zookeeper-3.5.7]# mkdir zkData // 建立目錄zkData
[root@sg-15 zkData]# vi myid // 建立一個 myid 的檔案
在檔案中新增與 server 對應的編號(注意:上下不要有空行,左右不要有空格)
5 // 192.168.0.215,這裡設定伺服器尾號,其他的不重複就行
[root@sg-15 conf]# mv zoo_sample.cfg zoo.cfg // 配置檔案改名
[root@sg-15 conf]# vi zoo.cfg //修改配置檔案
dataDir = /opt/module/zookeeper-3.5.7/zkData // 通常修改的路徑
// 新增配置
#######################cluster##########################
server.5=192.168.0.215:2888:3888
server.6=192.168.0.216:2888:3888
server.7=192.168.0.217:2888:3888
##配置引數解讀
server.A=B:C:D
A 是一個數字,表示這個是第幾號伺服器,就是myid中的值
B 是這個伺服器的地址
C 是這個伺服器Follower 與叢集中的 Leader 伺服器交換資訊的埠;
D 是萬一叢集中的 Leader 伺服器掛了,需要一個埠來重新進行選舉,選出一個新的
Leader,而這個埠就是用來執行選舉時伺服器相互通訊的埠。
注意:一臺機器弄好之後,打包傳送到其他機器。其他機器修改myid值
tar -cf module.tar ./module // 打包
scp -r module.tar root@192.168.0.216:/opt/ //傳送
tar -xf module.tar // 解包
3.4啟動zookeeper叢集
注意:叢集只要有半數以上包括半數就可正常服務。
三臺機器啟動:
[root@sg-15 bin]# ./zkServer.sh start //啟動
[root@sg-15 bin]# ./zkServer.sh restart //重啟
[root@sg-17 bin]# ./zkServer.sh status //檢查狀態
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader //本機器為leader
[root@sg-15 bin]# ./zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower //本機器為follower
4.選舉機制
選舉誰當leader
介紹:
SID:伺服器ID。用來唯一標識一臺ZooKeeper叢集中的機器,每臺機器不能重複,和myid一致。
ZXID:事務ID。ZXID是一個事務ID,用來標識一次伺服器狀態的變更。在某一時刻, 叢集中的每臺機器的ZXID值不一定完全一樣
Epoch:每個Leader任期的代號。沒有Leader時同一輪投票過程中的邏輯時鐘值是
4.1觸發選舉時機
1.伺服器剛啟動
2.伺服器執行期間無法和leader保持連線
當一臺機器進入leader選舉流程時,當前叢集出現兩種狀態:
1.叢集中已經存在一個leader(此機器和leader建立連線,並狀態同步)
2.叢集中不存在leader(觸發選舉),looking狀態
4.2zookeeper選舉機制---第一次啟動
伺服器1:myid=1
伺服器2:myid=2
伺服器3:myid=3
(1) 伺服器1啟動,發起一次選舉。伺服器1投自己一票。此時伺服器1票數一票,不夠半數以上(3票),選舉無法完成,伺服器1狀態保持為LOOKING;
(2) 伺服器2啟動,再發起一次選舉。伺服器1和2分別投自己一票並交換選票資訊:伺服器1和伺服器2比較誰的myid大,更改選票為推舉伺服器myid大的。此時伺服器1票數0票,伺服器2票數2票,大於半數以上結果,選舉完成。伺服器1為follower,2狀態為leader
(3) 伺服器3啟動,發起一次選舉。此時伺服器1,2已經不是LOOKING狀態,不會更改選票資訊。交換選票資訊結果:伺服器3為1票,此時伺服器3服從多數,更改選票資訊為伺服器2,並更改狀態為FOLLOWING;
4.2zookeeper選舉機制---非第一次啟動
伺服器1:myid=1
伺服器2:myid=2
伺服器3:myid=3
伺服器執行期間無法和leader保持連線觸發重新選舉:
假設zookeeper由5臺伺服器組成,SID分別為1,2,3,4,5,ZXID分別為8,8,8,7,7,並且此時SID為3的伺服器此時時leader。某時刻3和5出現故障,因此開始進行leader選舉:
SID為1的機器:(1,8,1)
SID為2的機器:(1,8,2)
SID為4的機器:(1,7,4)
選舉規則:
優先比較SID,再比較ZXID,其次比較Epoch。大的直接勝出當選leader
4.3選舉機制總結
半數機制,超過半數的投票通過,即通過。
第一次啟動選舉規則:投票過半數時,伺服器 myid 大的勝出當leader
第二次啟動選舉規則:①EPOCH大的直接勝出 ②EPOCH相同,事務id大的勝出 ③事務id相同,任期代號id大的勝出
5.客戶端命令列操作
5.1常用命令整合
登陸客戶端操作:很多命令和linux命令相似,比如ls,history等
jps : 檢視zookeeper執行的程式
help :顯示所有操作命令
ls / :檢視當前znode中包含的內容
ls -s / :檢視當前節點詳細資料
create : 建立普通節點
create -s :建立帶序號節點
create -e :建立臨時普通節點
create -e -s :建立臨時有序節點
delete:刪除節點
create /wangzhe "this is wangzhe" //新建節點(永久節點,不帶序號)
create /wangzhe/fashi "this is fashi" // //新建節點(永久節點,不帶序號)
get /wangzhe // this is wangzhe 取值
get -s /wangzhe //節點詳情
set set /wangzhe "this is wangzherongyao" //修改值
get -w :監聽值
ls -w /wangzhe :監聽數量
監聽註冊一個生效一次
5.2啟動客戶端
[root@sg-15 bin]# ./zkCli.sh -server 192.168.0.215:2181
quit //退出
5.3znode節點資料資訊
命令:ls -s /
[zk: 192.168.0.215:2181(CONNECTED) 3] ls -s /
[zookeeper]cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
##################################
1):czxid:建立節點的事務id zxid
每次修改ZooKeeper 狀態都會產生一個ZooKeeper 事務 ID。事務 ID 是ZooKeeper 中所有修改總的次序。每次修改都有唯一的 zxid,如果 zxid1 小於 zxid2,那麼zxid1 在 zxid2 之前發生。
2):ctime:znode 被建立的毫秒數(從 1970 年開始)
3):mzxid:znode 最後更新的事務zxid
4):mtime:znode 最後更新的毫秒數(從 1970 年開始)
5):pZxid:znode 最後更新的子節點zxid
6):cversion:znode 子節點變化號,znode 子節點修改次數
7):dataversion:znode 資料變化號
8):aclVersion:znode 訪問控制列表的變化號
9):ephemeralOwner:如果是臨時節點,這個是 znode 擁有者的 session id。如果不是臨時節點則是 0。
10):dataLength:znode 的資料長度
11):numChildren:znode 子節點數量
5.4節點型別(持久/短暫/有序號/無序號)
持久:客戶端和服務端端開連線後,建立的節點不刪除
序號:在分散式系統中,順序號可以被用於所有事件排序,這樣客戶端可以通過順序號推斷事件的順序
{
持久有序號:客戶端和服務端端開連線後,建立的節點不刪除。且節點名稱順序編號
持久無序號:客戶端和服務端端開連線後,建立的節點不刪除。無序號
}
短暫:客戶端和服務端端開連線後,建立的節點自己刪除
{
短暫有序號:客戶端和服務端端開連線後,建立的節點自己刪除。且節點名稱順序編號
短暫無序號:客戶端和服務端端開連線後,建立的節點自己刪除。無序號
}
5.5節點操作
5.5.1建立/刪除節點
create : 建立普通節點
create -s :建立帶序號節點
create -e :建立臨時普通節點
create -e -s :建立臨時有序節點
delete:刪除一個節點(如果這個節點下有子節點,刪除失敗)
deleteall:遞迴刪除所有節點(如果這個節點下有子節點,全部刪除)
eg:delete /wangzhe/fashi //只刪除fashi節點
deleteall /wangzhe //遞迴刪除wangzhe所有節點
//1.建立節點:普通永久節點
[zk: 192.168.0.215:2181(CONNECTED) 27] create /wangzhe "this is wangzhe"
Created /wangzhe
[zk: 192.168.0.215:2181(CONNECTED) 40] create /wangzhe/fashi "this is fashi"
Created /wangzhe/fashi
//2.建立節點:有序永久節點
[zk: 192.168.0.215:2181(CONNECTED) 66] create -s /wangzhe/fashi/daji "daji"
Created /wangzhe/fashi/daji0000000000
[zk: 192.168.0.215:2181(CONNECTED) 70] create -s /wangzhe/fashi/daji "daji"
Created /wangzhe/fashi/daji0000000001 //再次建立daji,序號+1
//3.建立節點:普通臨時節點
[zk: 192.168.0.215:2181(CONNECTED) 71] create -e /wangzhe/fashi/ganjiang "ganjiang"
Created /wangzhe/fashi/ganjiang
//4.建立節點:有序臨時節點
[zk: 192.168.0.215:2181(CONNECTED) 73] create -e -s /wangzhe/fashi/anqila "anqila"
Created /wangzhe/fashi/anqila0000000003
//檢視
[zk: 192.168.0.215:2181(CONNECTED) 74] ls /wangzhe/fashi
[anqila0000000003, daji0000000000, daji0000000001, ganjiang]
//delete刪除
[zk: 192.168.0.215:2181(CONNECTED) 19] delete /wangzhe/fashi/xiaoqiao
xiaoqiao xiaoqiao0000000007
5.5.2獲取/檢視的值
注意:get獲取有序節點的值時:必須獲取最後一個序號的值,比如獲取下面ganjiang0000000004報錯,獲取ganjiang0000000005正常。
ls
get
get -s
//檢視
[zk: 192.168.0.215:2181(CONNECTED) 10] ls /wangzhe/fashi
[daji0000000000, daji0000000001, ganjiang0000000004, ganjiang0000000005, xiaoqiao]
//獲取值
[zk: 192.168.0.215:2181(CONNECTED) 12] get /wangzhe/fashi/xiaoqiao0000000007
xiaoqiao
// 獲取詳細
[zk: 192.168.0.215:2181(CONNECTED) 13] get -s /wangzhe/fashi/xiaoqiao0000000007
xiaoqiao
cZxid = 0x30000002f
ctime = Sat Mar 26 18:37:35 CST 2022
mZxid = 0x30000002f
mtime = Sat Mar 26 18:37:35 CST 2022
pZxid = 0x30000002f
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0
[zk: 192.168.0.215:2181(CONNECTED) 14] get -s /wangzhe/fashi
this is fashi
cZxid = 0x300000020
ctime = Sat Mar 26 17:31:12 CST 2022
mZxid = 0x300000020
mtime = Sat Mar 26 17:31:12 CST 2022
pZxid = 0x30000002f
cversion = 10
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 13
numChildren = 6
5.6節點監聽
get -w :監聽值
ls -w /wangzhe :監聽數量
監聽註冊一個生效一次
5.6.1監聽節點值改變
[zk: 192.168.0.215:2181(CONNECTED) 25] get -w /wangzhe //開啟一次監聽
this is wangzhe
[zk: 192.168.0.215:2181(CONNECTED) 26] set /wangzhe "jianting:this is wangzhe" //修改值
WATCHER:: //監聽值發生變化
WatchedEvent state:SyncConnected type:NodeDataChanged path:/wangzhe
5.6.2監聽節點下子節點數量改變
[zk: 192.168.0.215:2181(CONNECTED) 27] ls -w /wangzhe ////開啟一次監聽
[fashi, fashi0000000001]
[zk: 192.168.0.215:2181(CONNECTED) 28] delete /wangzhe/fashi0000000001 //刪除一個節點
WATCHER:: //監聽數量發生變化
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/wangzhe
6.goang操作zookeeper
6.1建立節點create(增)
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
// 任意一個ip都可以,但是建議放主節點ip
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
panic(err)
}
defer conn.Close()
// 1.建立的普通永久節點
path, err := conn.Create("/hello", []byte("world"), 0, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("err:",err)
}
fmt.Println("建立的普通永久節點:", path)
// 2.建立的普通臨時節點,建立此節點的會話結束後立即清除此節點
ephemeral, err := conn.Create("/ephemeral", []byte("world"), zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("err:",err)
}
fmt.Println("建立的普通臨時節點:", ephemeral)
// 3.建立的有序永久節點
sequence, err := conn.Create("/sequence", []byte("world"), zk.FlagSequence, zk.WorldACL(zk.PermAll))
if err != nil {
panic(err)
}
fmt.Println("建立的有序永久節點:", sequence)
// 4.建立的有序臨時節點,建立此節點的會話結束後立即清除此節點
ephemeralSequence, err := conn.Create("/ephemeralSequence", []byte("world"), zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll))
if err != nil {
panic(err)
}
fmt.Println("建立的有序臨時節點:", ephemeralSequence)
}
6.2檢視節點get(查)
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
result, state, err := conn.Get("/wangzhe/fashi")
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Println("result: ", string(result)) // this is fashi
fmt.Printf("%#v",state)
//狀態結果:&zk.Stat{Czxid:12884901920, Mzxid:12884901920, Ctime:1648287072539, Mtime:1648287072539, Version:0, Cversion:11, Aversion:0, EphemeralOwner:0, DataLength:13, NumChildren:5, Pzxid:12884901936}
}
6.3修改節點set(改)
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
path := "/wangzhe/fashi"
_, state, _ := conn.Get(path) // 先查詢,拿到當前版本
state, err = conn.Set(path, []byte("hello"), state.Version)
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Printf("%#v",state)
//結果:&zk.Stat{Czxid:12884901920, Mzxid:12884902012, Ctime:1648287072539, Mtime:1648305221598, Version:1, Cversion:11, Aversion:0, EphemeralOwner:0, DataLength:5, NumChildren:5, Pzxid:12884901936}2022/03/26 22:33:39 recv loop
}
6.4刪除節點delete(刪)
注意:此方法不能遞迴刪除節點
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
path := "/hello"
exists, state, err := conn.Exists(path) //判斷是否存在和查詢版本
if exists{
err = conn.Delete(path, state.Version)
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Println("節點刪除成功!!!")
}else {
fmt.Println("節點不存在!!!")
}
}
6.5檢視節點的子節點Children
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
childrenList, state, err := conn.Children("/wangzhe/fashi")
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Println(childrenList)
fmt.Printf("%#v",state)
}
6.6遍歷節點Children後再get
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
childrenList, _, err := conn.Children("/wangzhe/fashi")
if err != nil {
fmt.Println("err:",err)
return
}
for _,children:=range childrenList{
childrenPath:= fmt.Sprintf("/wangzhe/fashi/%s",children)
result, state, err := conn.Get(childrenPath)
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Println(string(result))
fmt.Println(state)
}
}
6.7判斷節點是否存在-conn.Exists
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
path := "/hello"
exists, _, err := conn.Exists(path)
if err != nil {
fmt.Println("err:",err)
return
}
if exists{
fmt.Println("節點存在")
}else{
fmt.Println("節點不存在")
}
}
7.監聽/watch
7.1監聽節點-全域性監聽
將監聽器放到Connect
函式中,如果有監聽事件發生,會一直執行監聽器的回撥函式。監聽執行了一次之後要重新註冊監聽。
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func callback(e zk.Event) {
fmt.Println("========================")
fmt.Println("path:", e.Path)
fmt.Println("type:", e.Type.String())
fmt.Println("state:", e.State.String())
}
func main() {
eventCallbackOption := zk.WithEventCallback(callback)
// 經過測試,在連線時會執行3次回撥函式
conn, _, err := zk.Connect([]string{"192.168.0.215","192.168.0.216","192.168.0.217"}, time.Second,eventCallbackOption)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
// 註冊一個 watch
exists, state, _, err := conn.ExistsW("/watch")
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Println("exists:",exists)
if !exists {
// 建立 /watch 時,觸發監聽事件,watch 失效
_, err = conn.Create("/watch", []byte("watch"), zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("err:",err)
return
}
// 再註冊一個 watch
_, state, _, err = conn.ExistsW("/watch")
if err != nil {
fmt.Println("err:",err)
return
}
}
// 刪除 /watch 時,觸發監聽事件,watch 失效
err = conn.Delete("/watch", state.Version)
if err != nil {
fmt.Println("err:",err)
return
}
}
結果:
========================
path:
type: EventSession
state: StateConnecting
========================
path:
type: EventSession
state: StateConnected
2022/03/27 11:09:27 connected to 192.168.0.215:2181
========================
path:
type: EventSession
state: StateHasSession
2022/03/27 11:09:27 authenticated: id=360292329056632906, timeout=4000
2022/03/27 11:09:27 re-submitting `0` credentials after reconnect
exists: false
========================
path: /watch
type: EventNodeCreated
state: Unknown
========================
path: /watch
type: EventNodeDeleted
state: Unknown
7.2監聽部分事件
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215","192.168.0.216","192.168.0.217"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
// 註冊一個 watch
exists, _, eventChannel, err := conn.ExistsW("/watch")
if err != nil {
fmt.Println("err:",err)
return
}
go func() {
// 從事件 channel 中取出事件
e := <-eventChannel
fmt.Println("========================")
fmt.Println("path:", e.Path)
fmt.Println("type:", e.Type.String())
fmt.Println("state:", e.State.String())
}()
if !exists {
// 建立 臨時節點/watch 時,觸發監聽事件,watch 失效
_, err = conn.Create("/watch", []byte("watch"), zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("err:",err)
return
}
}
}
8.微服務動態上下線監聽(服務註冊/發現)
8.1需求實現
微服務分散式系統中,主節點可以有多臺,可以動態上下線,任意一臺客戶端都能實時感知到主節點伺服器的上下線。
需求實現:
服務端:服務端啟動時,在zookeeper中建立臨時有序節點,服務關閉時,臨時節點自動刪除了(zookeeper臨時節點機制)
客戶端:監聽節點的變化
8.2服務端建立程式碼-(註冊服務)
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
//建立的有序臨時節點,建立此節點的會話結束後立即清除此節點 create -e -s
ephemeralSequence, err := conn.Create("/servers/bikesvc", []byte("bikesvc"), zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("err:",err)
return
}
fmt.Println("建立的有序臨時節點:", ephemeralSequence)
time.Sleep(time.Second*10)
}
8.3客戶端監聽程式碼-(服務發現)
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func mirror(conn *zk.Conn, path string) (chan []string, chan error) {
snapshots := make(chan []string)
errors := make(chan error)
go func() {
for {
snapshot, _, events, err := conn.ChildrenW(path)
if err != nil {
errors <- err
return
}
snapshots <- snapshot
evt := <-events
if evt.Err != nil {
errors <- evt.Err
return
}
}
}()
return snapshots, errors
}
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181", "192.168.0.216:2181", "192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:", err)
return
}
defer conn.Close()
snapshots, errors := mirror(conn, "/servers") //監控的根節點,根節點不能刪除
go func() {
for {
select {
case snapshot := <-snapshots:
fmt.Println("監控變化:", snapshot)
case err := <-errors:
fmt.Println("err:", err)
}
}
}()
for {
}
}
結果:
服務端:
建立的有序臨時節點: /servers/bikesvc0000000010
客戶端:
監控變化: []
監控變化: [bikesvc0000000009]
監控變化: []
9.分散式鎖
加鎖進行資源保護
go-zookeeper 新增分散式鎖的方法為NewLock(c *Conn, path string, acl []ACL)。
鎖的結構體為:
type Lock struct {
c *Conn
path string
acl []ACL
lockPath string
seq int
}
這個結構體實現了三個方法:Lock(),LockWithData(data []byte)和Unlock()
9.1分散式鎖案例
根節點“/root”判斷是否存在,不存在則建立
package main
import (
"fmt"
"sync"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
lock := zk.NewLock(conn, "/root/lock", zk.WorldACL(zk.PermAll)) //加鎖
err = lock.LockWithData([]byte("it is a lock"))
if err != nil {
panic(err)
}
fmt.Println("第", n, "個 goroutine 獲取到了鎖")
time.Sleep(time.Second*1) // 1 秒後釋放鎖
lock.Unlock() //解鎖
}(i)
}
wg.Wait()
}
這裡給了兩個程式搶鎖,ls檢視一下鎖:
解釋:把所有程式按有序排列,當成節點放入lock節點中,按照最小的序號執行。解鎖一個刪除一個。直到節點為空,程式執行完畢。
[zk: localhost:2181(CONNECTED) 32] ls /root/lock
[_c_1dbbc1ec75b285ef10a6d6154627335c-lock-0000000153, _c_793a837ded040d01608395e5eac65979-lock-0000000152]
先執行152,再執行153
9.2監控鎖案例
監控鎖節點變化
監控程式碼
package main
import (
"fmt"
"time"
"github.com/go-zookeeper/zk"
)
func mirror(conn *zk.Conn, path string) (chan []string, chan error) {
snapshots := make(chan []string)
errors := make(chan error)
go func() {
for {
snapshot, _, events, err := conn.ChildrenW(path)
if err != nil {
errors <- err
return
}
snapshots <- snapshot
evt := <-events
if evt.Err != nil {
errors <- evt.Err
return
}
}
}()
return snapshots, errors
}
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181", "192.168.0.216:2181", "192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:", err)
return
}
defer conn.Close()
snapshots, errors := mirror(conn, "/root/lock") //監控的根節點,根節點不能刪除
go func() {
for {
select {
case snapshot := <-snapshots:
fmt.Println("監控變化:", snapshot)
case err := <-errors:
fmt.Println("err:", err)
}
}
}()
for {
}
}
分散式鎖程式碼
package main
import (
"fmt"
"sync"
"time"
"github.com/go-zookeeper/zk"
)
func main() {
conn, _, err := zk.Connect([]string{"192.168.0.215:2181","192.168.0.216:2181","192.168.0.217:2181"}, time.Second)
if err != nil {
fmt.Println("err:",err)
return
}
defer conn.Close()
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
lock := zk.NewLock(conn, "/root/lock", zk.WorldACL(zk.PermAll)) //加鎖
err = lock.LockWithData([]byte("it is a lock"))
if err != nil {
panic(err)
}
fmt.Println("第", n, "個 goroutine 獲取到了鎖")
time.Sleep(time.Second*1) // 1 秒後釋放鎖
lock.Unlock() //解鎖
}(i)
}
wg.Wait()
}
結果: