看完這篇文章你就可以告訴領導你精通Zookeeper了

CodeTiger發表於2022-04-04

一、Zookeeper概述

1、概述

Zookeeper 是一個開源的為分散式框架提供協調服務的 Apache 專案。在分散式系統中,扮演註冊中心的角色。

Zookeeper資料模型的結構與Linux檔案系統很像,整體上可以看做一棵樹,從根節點往下,每個節點稱為ZNode。每一個ZNode預設能夠儲存1MB的資料,每個ZNode都對應一個唯一的路徑,類似於Linux中的檔案路徑。

image-20211129213010899.png

Zookeeper從設計模式角度來理解:是一個基於觀察者模式設計的分散式服務管理框架,它負責儲存和管理大家都關心的資料,然後接受觀察者的註冊,一旦這些資料的狀態發生變化,Zookeeper就 將負責通知已經在Zookeeper上註冊的那些觀察者,做出相應的反應。

image-20211129214320967.png

2、特點

(1)Zookeeper:一個領導者(Leader),多個跟隨者(Follower)組成的叢集。

(2)叢集中只要有半數以上節點存活,Zookeeper叢集就能正常提供服務。所以Zookeeper叢集適合安裝奇數臺伺服器。

(3)全域性資料一致:每個Server儲存一份相同的資料副本,Client無論連線到哪個Server,資料都是一樣的。

(4)更新請求順序執行,來自同一個Client的更新請求按其傳送順序依次執行。

(5)資料更新具有原子性,一次更新要麼成功要麼失敗。

(6)實時性,在一定時間範圍內,Client能讀到最新資料。

image-20211204150309944.png

3、應用場景

Zookeeper能提供的服務包括:統一命名服務、統一配置管理、統一叢集管理、伺服器節點動態上下線、軟負載均衡等。
image-20211204151153948.png

(1)統一命名服務

在分散式環境下,經常需要對應用或者服務進行統一命名,便於識別。例如IP和域名的對應關係,一般我們都是輸入域名即可訪問對應網站,但其實內部使用DNS把域名解析成了對應的IP地址。
image-20211204152512415.png
(2)統一配置管理

在分散式系統中,一個叢集裡各個伺服器的配置檔案在修改後經常需要進行同步,手動同步容易出錯並且耗時耗力,利用Zookeeper監聽的功能,可以很好的實現這個功能。

可以將共同的配置檔案寫入Zookeeper的一個節點中,然後各個Client監聽這個節點,一旦節點中的內容發生變化時,Zookeeper就通知各個Client。
image-20211204153525895.png
(3)統一叢集管理

分散式環境中,實時監控節點的狀態是很有必要的,這樣可以依據節點的狀態動態的做出一些調整。可以將節點資訊寫入Zookeeper的ZNode中,然後監聽這個ZNode就可以獲取它的實時狀態變化了。
image-20211204154306198.png
(4)伺服器動態上下線通知

這個第一小節就講了。
image-20211129214320967.png
(5)軟負載均衡

在Zookeeper中記錄每臺伺服器的訪問數,這樣有新的客戶端請求到來時,可以讓訪問數最少的伺服器處理請求。
image-20211204154956362.png

二、Zookeeper安裝配置啟動

1、安裝

進入下載地址,選擇對應的版本下載
image-20211204155320214.png
下載後放到Linux某個目錄下解壓即可。

2、配置

解壓完畢後,來到zookeeper資料夾conf目錄下拷貝一份配置檔案,命名為zoo.cfg
image-20211204160209840.png
將dataDir的目錄修改成自定義的目錄,因為/tmp目錄下的檔案容易被清理掉。
image-20211204160405577.png
除了dataDir,上面還有幾個配置項如下:

(1)tickTime,預設2000ms。通訊心跳時間,即客戶端和伺服器或者伺服器和伺服器之間2s會傳送一次心跳,檢測機器的工作狀態是否正常。

(2)initLimit,預設10次。LF初始通訊時限,即叢集中的FL(Follow和Leader)伺服器之間初始連線時能容忍的最多心跳數(tickTime的數量)。

(3)syncLimit,預設5次。LF同步通訊時限,即叢集中的FL(Follow和Leader)伺服器之間一次請求和響應能容忍的最大心跳數。LF之間如果通訊時間超過syncLimit * tickTime,Leader則會認為Follow出故障了,將會從伺服器列表中刪除Follow。

(4)dataDir,Zookeeper資料存放路徑。

(5)clientPort,客戶端連線埠,預設2181。

3、啟動

# 可選引數
./zkServer.sh [--config <conf-dir>] {start|start-foreground|stop|restart|status|print-cmd}
# 啟動
./zkServer.sh start
# 檢視狀態
./zkServer.sh status
# 重啟
./zkServer.sh restart
# 停止
./zkServer.sh stop

# 啟動客戶端
./zkCli.sh
# 退出客戶端
[zk: localhost:2181(CONNECTED) 3] quit

三、Zookeeper叢集配置

因為Zookeeper叢集的規則是半數以上的伺服器可用則這個叢集可用,所以叢集中最少需要3臺以上的伺服器並且伺服器的數量最好是奇數

為什麼是奇數,因為在相同容錯能力的情況下,奇數臺伺服器更節省資源。比如3臺伺服器的叢集,至少要2臺以上伺服器正常工作這個叢集才可用,也就是最多允許1臺伺服器當機;4臺伺服器的叢集,至少需要3臺以上伺服器正常工作這個叢集才可用,也就是最多允許1臺伺服器當機。3臺或者4臺伺服器的叢集,都至多允許一臺伺服器當機,容錯能力相同的情況下,4臺伺服器就浪費了一臺的數量。

另外一個原因是因為follow在選舉leader的時候,要求可用節點數量 > 總節點數量 / 2。如果叢集的節點數量為偶數臺,那麼就可能出現無法選舉出Leader的情況。具體參考這篇文章

簡單點,這裡我們的叢集中有3個節點,即3個Zookeeper伺服器。

1、解壓安裝

跟上面單機一樣,分別下載解壓到3臺伺服器的指定目錄。

2、配置伺服器編號

在伺服器的zookeeper資料夾的根目錄下建立zkDatas資料夾,並在zkDatas資料夾下建立一個myid的檔案,在檔案中新增server對應的編號,叢集中唯一。

cd /usr/local/apache-zookeeper-3.5.7
mkdir zkDatas
cd zkDatas
vi myid

## 伺服器1的myid檔案內容如下,對應的,伺服器2的myid內容為2,伺服器3的myid內容為3
1

3、配置zoo.cfg檔案

配置三個伺服器的zoo.cfg檔案內容如下

# 拷貝zoo_sample.cfg檔案
cp -r zoo_sample.cfg zoo.cfg
vi zoo.cfg

## zoo.cfg檔案內容如下
dataDir=/usr/local/apache-zookeeper-3.5.7/zkDatas
#######################cluster##########################
server.1=192.168.1.128:2888:3888
server.2=192.168.1.129:2888:3888
server.3=192.168.1.130:2888:3888

關於上面叢集的配置server.A=B:C:D解釋如下

  • A是一個數字,表示第幾號伺服器。之前我們配置了myid檔案,A的值就是myid檔案中的值。zookeeper在啟動的時候會讀取此檔案,拿到裡面的檔案內容後再和zoo.cfg檔案裡的配置進行比較,判斷是哪個伺服器。
  • B代表伺服器的地址。
  • C是Leader和Follow交換資訊的埠。
  • D是當叢集中的Leader伺服器歇逼的時候,重新選舉時的通訊埠。

4、啟動各個伺服器

啟動完後,就會告訴你誰是leader誰是follow。

[root@localhost apache-zookeeper-3.5.7]# bin/zkServer.sh status
JMX enabled by default
Using config: /usr/local/apache-zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: follower

[root@localhost apache-zookeeper-3.5.7]# bin/zkServer.sh status
JMX enabled by default
Using config: /usr/local/apache-zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: leader

[root@localhost apache-zookeeper-3.5.7]# bin/zkServer.sh status
JMX enabled by default
Using config: /usr/local/apache-zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: follower

四、Zookeeper的選舉機制

單機模式下不涉及選舉,只有在叢集模式下才會進行選舉。Leader選舉一般發生在下面兩種情況:第一次啟動叢集的時候和叢集執行過程中Leader掛了。這兩種情況下Leader的選舉機制時不同的,需要分開討論。

在學習選舉機制之前,需要先學習幾個概念。

  • SID:伺服器ID,即myid檔案中的值,用來唯一標識一臺Zookeeper叢集中的機器,不能重複。
  • ZXID:zookeeper transaction id,即zookeeper事務id,用來標識一次伺服器狀態的變更。在某一時刻,叢集中每臺機器的zxid大概率是不同的。
  • Epoch:邏輯時鐘,也叫投票的次數,每投完一次票這個值就會增加,同一輪投票過程中邏輯時鐘的值是相同的。

1、第一次啟動時的選舉機制

image-20211204200013593.png
(1)server1啟動後,發起一次選舉。server1投自己一票,此時server1共有1票,不夠半數以上(3票),無法產生Leader,server1狀態保持LOOKING。

(2)server2啟動,發起一次選舉。server1和server2各投自己一票並交換選票資訊,此時server1發現server2的SID比自己目前投票的(也就是它自身)更大,於是更改投票為server2。此時server1共有0票,server2共有2票,不夠半數以上,無法產生leader,server1和server2狀態保持LOOKING。

(3)server3啟動,發起一次選舉。和(2)一樣,server1和server2最終都會把票投給server3,那麼此時server1和server2的票數就為0,而server3的票數為3,超過了半數,server3變成leader。server1和server2更改狀態為FOLLOWING,server3更改狀態為LEADING。

(4)server4啟動,發起一次選舉。此時server1和server2都不是LOOKING狀態,所以不會更改選票資訊,所以最終server4少數服從多數,把選票投給server3,此時server3總票數為4,其他三臺伺服器總票數都為0。

(5)server4啟動,發起一次選舉。和(4)一樣的流程。

2、非第一次啟動時的選舉機制

當叢集中出現下面的情況時,會觸發leader選舉機制

(1)叢集執行過程中,有新的伺服器節點加入。

(2)follow伺服器無法和leader伺服器通訊時,follow會認為leader掛了。

當觸發選舉機制時,叢集可能存在兩種情況

(1)叢集中此時已經存在leader。那麼在這種情況下,只需要告訴發起選舉的伺服器有關leader的相關資訊,讓該伺服器和leader建立連線並進行狀態同步即可。

(2)叢集中此時不存在leader。

還是用上一小節的圖來說,叢集中5臺server,SID分別是1,2,3,4,5,ZXID分別是8,8,8,7,7,server3是leader。此時server3和server5掛了,follow伺服器都會把自己的狀態變成LOOKING,開始進行leader選舉。

這裡就需要用到上面說過的三個概念:Epoch、SID和ZXID。選舉Leader規則:

  • Epoch大的直接勝出
  • Epoch相同,ZXID大的勝出
  • ZXID相同,SID大的勝出

對於剩下的server1、server2和server4,它們的(Epoch, ZXID, SID)的值分別是(1, 8, 1)、(1, 8, 2)、(1, 7, 4)。所以最後的leader就是server2。

五、客戶端相關的命令

# 啟動客戶端
./zkCli.sh
# help命令,可以顯式所有的操作命令
[zk: localhost:2181(CONNECTED) 0] help

image-20211204202928346.png
一些比較常見的如下

(1)ls [-s] [-w] [-R] path,檢視對應path下的子節點,-w表示監聽子節點變化,-s附加次級資訊。

(2)create path,在對應path下建立一個節點,-s表示節點帶序號,-e表示建立臨時節點,重啟後或者超時就會被刪除。

(3)get path,獲得節點的值(可監聽)。-w表示監聽節點內容變化,-s表示附加次級資訊。

(4)set path data,設定節點的具體值。

(5)stat path,檢視節點狀態。

(6)delete path,刪除節點。

(7)deleteall path,遞迴刪除節點。

1、檢視ZNode節點資訊

[zk: localhost:2181(CONNECTED) 4] ls /
[zookeeper]

[zk: localhost:2181(CONNECTED) 5] 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

ls -s path可以檢視節點的詳細資料。

(1)cZxid:建立節點的事務id

每次修改 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 子節點數量

2、建立ZNode節點

Zookeeper中節點型別可以大致分為兩類持久化(Persistent)節點臨時(Ephemeral)節點,又可以細分為四類:

(1)持久化目錄節點

客戶端與Zookeeper斷開連線後,該節點依舊存在。

(2)持久化順序編號目錄節點

客戶端與Zookeeper斷開連線後,該節點依舊存在,只是Zookeeper給該節點名稱進行順序編號。

(3)臨時目錄節點

客戶端與Zookeeper斷開連線後,該節點被刪除。

(4)臨時順序編號目錄節點

客戶端與 Zookeeper 斷開連線後,該節點被刪除,只是Zookeeper給該節點名稱進行順序編號。
image-20211204205549310.png
建立znode時設定順序標識,znode名稱後會附加一個值,順序號是一個單調遞增的計數器,由父節點維護。

注意:在分散式系統中,順序號可以被用於為所有的事件進行全域性排序,這樣客戶端可以通過順序號推斷事件的順序

2.1 建立不帶序號的永久節點
[zk: localhost:2181(CONNECTED) 14] create /znode1 "value1"
Created /znode1

[zk: localhost:2181(CONNECTED) 15] create /znode2 "value2"
Created /znode2

# 獲得節點的值
[zk: localhost:2181(CONNECTED) 16] get -s /znode1
value1
cZxid = 0x6
ctime = Sat Dec 04 18:43:50 CST 2021
mZxid = 0x6
mtime = Sat Dec 04 18:43:50 CST 2021
pZxid = 0x6
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
2.2 建立帶序號的永久節點
[zk: localhost:2181(CONNECTED) 17] create -s /znode1/znode3 "value3"
Created /znode1/znode30000000000

[zk: localhost:2181(CONNECTED) 18] create -s /znode1/znode4 "value4"
Created /znode1/znode40000000001

[zk: localhost:2181(CONNECTED) 19] create -s /znode5 "value5"
Created /znode50000000003

[zk: localhost:2181(CONNECTED) 20] create -s /znode1/znode6 "value6"
Created /znode1/znode60000000002

可以發現如果原來沒有序號節點,序號從 0 開始依次遞增。如果原節點下已有 2 個節點,則再排序時從 2 開始,以此類推。

2.3 建立臨時節點
[zk: localhost:2181(CONNECTED) 21] create -e /znode2/znode7 "value7"
Created /znode2/znode7

[zk: localhost:2181(CONNECTED) 22] create -e -s /znode2/znode8 "value8"
Created /znode2/znode80000000001

[zk: localhost:2181(CONNECTED) 23] ls /znode2
[znode7, znode80000000001]

# 重啟客戶端資料消失
2.4 修改節點的值
[zk: localhost:2181(CONNECTED) 24] set /znode2/znode7 "val"

[zk: localhost:2181(CONNECTED) 25] get /znode2/znode7
val

3、監聽節點

3.1 監聽器原理

客戶端註冊監聽它關心的目錄節點,當目錄節點發生變化時(資料改變、節點刪除、子目錄節點增加刪除),Zookeeper會通知客戶端。
image-20211204231617358.png
(1)首先要有一個main()執行緒。

(2)在main執行緒中建立zookeeper客戶端,這時就會建立兩個執行緒,一個負責網路連線通訊(connect),一個負責監聽(listener)。

(3)通過connect執行緒將註冊的監聽事件傳送給zookeeper。

(4)將監聽事件新增到zookeeper的註冊監聽列表中。

(5)zookeeper監聽到有資料或路徑變化時,就會通知listener執行緒。

(6)listener執行緒內部呼叫process方法。

3.2 常見的監聽

(1)監聽節點資料的變化:get -w path

(2)監聽子節點增減的變化:ls -w path

注意:上面兩種監聽都是註冊一次生效一次。如果想要多次生效,那麼就要註冊多次。

4、刪除和檢視節點

# 檢視/znode2下的子節點
[zk: localhost:2181(CONNECTED) 1] ls /znode2
[znode7, znode80000000001]

# 刪除/znode2節點,因為下面存在子節點,所以不能刪除
[zk: localhost:2181(CONNECTED) 2] delete /znode2
Node not empty: /znode2

# 刪除/znode2/znode80000000001節點
[zk: localhost:2181(CONNECTED) 3] delete /znode2/znode80000000001

[zk: localhost:2181(CONNECTED) 4] ls /znode2
[znode7]

[zk: localhost:2181(CONNECTED) 5] delete /znode2
Node not empty: /znode2

# 刪除/znode2及其下面所有子節點
[zk: localhost:2181(CONNECTED) 6] deleteall /znode2

[zk: localhost:2181(CONNECTED) 7] ls /znode2
Node does not exist: /znode2

# 檢視節點狀態
[zk: localhost:2181(CONNECTED) 9] stat /znode1
cZxid = 0x6
ctime = Sat Dec 04 18:43:50 CST 2021
mZxid = 0x6
mtime = Sat Dec 04 18:43:50 CST 2021
pZxid = 0xb
cversion = 3
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 3

六、客戶端相關的API

除了在啟動zkCli.sh在命令列中使用相關的命令,也可以使用程式語言操作zookeeper相關的api。

首先建立一個Java的maven專案,在pom.xml檔案中引入zookeeper相關的依賴

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

zookeeper相關的api可以參考官方文件:點選連結

為了便於測試,可以引入junit的依賴

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>RELEASE</version>
</dependency>

client寫資料的時候,寫請求可能是傳送給Leader也可能是Follow。假設叢集中有3臺伺服器,1臺Leader,2臺Follow

(1)傳送給Leader節點時
image-20211205162442914.png
當叢集中半數以上節點完成write請求後,就開始開放給客戶端請求了。

(2)傳送給Follow節點時
image-20211205162535496.png

當叢集中半數以上節點完成write請求後,就開始開放給客戶端請求了。

七、Zookeeper實現分散式鎖

可以參考之前的文章:Zookeeper + Curator實現分散式鎖

文章首發於我的公眾號【禿頭哥程式設計】,歡迎大家關注

相關文章