分布式協調服務——zookeeper

1 : zookeeper 概述:
1.1 : zookeeper簡介:
zookeeper是一個分布式的, 開源的分布式應用程式協調服務,是對Google的Chubby組件的開源實作,為Hadoop和HBase的運行提供了相應的服務.他是一個為分布式應用提供一致性服務的軟體,提供的功能包括:配置維護(使得集群中的機器可以共享配置資訊中的那些公共的部分);命名服務(是指通過指定的名字來獲取資環或者服務的地址,以及提供者的資訊,利用zookeeper可以很容易的創建一個全域的路徑,而這個路徑就可以作為一個名字,可以指向集群中的機器,提供的服務的地址,遠程物件等);分布式同步(可以使得集群中各個節點具有相同的系統狀態);組服務(通過zookeeper的短暫節點的特征,來監控每個應用程式的上線和下線)等,他是用java語言來撰寫,通過Zab協議來保證節點的一致性,zookeeper的目標就是封裝好復雜容易出錯的關鍵服務,將簡單易用的介面和性能高效,功能穩定的提供給用戶,
1.2:zookeeper的特點:
- 最終一致性:客戶端無論連接到哪個server,展示給他的都是同一個視圖,這就是zookeeper的重要特點之一,
- 可靠性:zookeeper具有簡單,健壯,良好的性能,如果一條訊息被一臺服務器接收,那么它將會被所有的服務器所接收,
- 實時性:zookeeper將保證客戶端將在一個時間間隔范圍內,獲得服務器的更新訊息或者服務器的失敗訊息,但是由于網路延時等原因,zookeeper不能保證兩個客戶端能同時得到剛剛更新的資料,如果需要最新的資料,那么就需要在讀資料之前呼叫syne( )介面,
- 等待無關:慢的或者失效的客戶端不得干預快速的客戶端的請求,這就是的每個客戶端都能夠有效的等待,
- 原子性:對zookeeper的更新要么全部成功,要么全部失敗,沒有中間狀態,
- 順序性:它包括全域有序和偏序兩種,全域有序(服務端)就是如果一臺服務器上訊息A在訊息B之前發送,則在所有server上訊息A都將在訊息B前被發布,偏序(客戶端)是指,如果一個訊息B在訊息A后被同一個發送者發布,A必將排在B前面.
1.3:zookeeper的基本架構
zookeeper服務自身組成一個集群(2n+1個節點,允許哪個節點失效),zookeeper服務具有三個角色,一個是Leader,一個是Follower,一個是Observer,
-
Leader :為客戶端提供讀寫服務,并維護集群狀態,它是由集群選舉所產生的;
-
Follower :為客戶端提供讀寫服務,并定期向 Leader 匯報自己的節點狀態,同時也參與寫操作“過半寫成功”的策略和 Leader 的選舉;
-
Observer :為客戶端提供讀寫服務,并定期向 Leader 匯報自己的節點狀態,但不參與寫操作“過半寫成功”的策略和 Leader 的選舉,因此 Observer 可以在不影響寫性能的情況下提升集群的讀性能,
-
下圖就是zookeeper的架構圖:

客戶端(Client)可以選擇連接到zookeeper集群中的每個服務端(server),并且每個服務端的資料完全相同,每個從節點都需要與主節點進行通信,并同步主節點上更新的資料,
對于zookeeper集群來說,zookeeper只要超過一般數量的服務端可用,那么zookeeper整體服務就是可用的,
1.4:zookeeper的作業原理:
zookeeper的核心原理就是原子廣播,該原子廣播就是對zookeeper集群上所有主機資料包,通過這個機制保證了各個服務端之前的資料同步,那么實作這個機制在zookeeper中有一個內部協議(Zab協議),這個協議有兩種模式,一個是恢復模式,一個是廣播模式,
當服務啟動或者是在主節點崩潰之后,Zab協議就進入了恢復模式,當主節點再次被選舉出來,且大多數服務端完成了和主節點的狀態同步之后,恢復模式就結束了,狀態同步保證了主節點和服務端具有相同的系統狀態,一旦主節點已經和多數的從節點(也就是服務節點)進行了狀態同步之后,他就可以開始廣播訊息即進入到了廣播模式,
在廣播模式下,服務端會接收客戶端的請求,所有的寫請求都被轉發給leader,再有leader將更新廣播給follower節點,當半數以上的follower完成資料寫請求之后,leader才會提交這個更新,然后客戶端才會收到一個更新成功的回應,
2:zookeeper安裝配置:
Zookeeper集群搭建指的是ZooKeeper分布式模式安裝,通常由2n+1臺server組成,這是因為為了保證Leader選舉(基于Paxos演算法的實作)能過得到多數的支持,所以ZooKeeper集群的數量一般為奇數,
Zookeeper運行需要java環境,所以需要提前安裝jdk,對于安裝leader+follower模式的集群,大致程序如下:
- 配置主機名稱到IP地址映射配置
- 修改ZooKeeper組態檔
- 遠程復制分發安裝檔案
- 設定myid
- 啟動ZooKeeper集群
如果要想使用Observer模式,可在對應節點的組態檔添加如下配置:
peerType=observer
其次,必須在組態檔指定哪些節點被指定為Observer,如:
server.1:node1:2181:3181:observer
這里,我們安裝的是leader+follower模式
主機規劃:
| 服務器IP | 主機名 | myid的值 |
|---|---|---|
| 192.168.88.161 | node1 | 1 |
| 192.168.88.162 | node2 | 2 |
| 192.168.88.163 | node3 | 3 |
地址規劃:
| 軟體名 | 地址 |
|---|---|
| zookeeper-3.4.6.tar.gz | /export/softwate |
| zookeeper-3.4.6 | /export/servers |
| zookeeper資料路徑 | /export/servers/zookeeper-3.4.6/zkdatas |
2.1:第一步:下載zookeeeper的壓縮包,
下載網址如下http://archive.apache.org/dist/zookeeper/
我們在這個網址下載我們使用的zk版本為3.4.6
下載完成之后,上傳到我們的linux的/export/software路徑下準備進行安裝
2.2:第二步:解壓
在node1主機上,解壓zookeeper的壓縮包到/export/server路徑下去,然后準備進行安裝
tar -zxvf zookeeper-3.4.6.tar.gz -C /export/servers/
3. 第三步:修改組態檔
在node1主機上,修改組態檔
cd /export/servers/zookeeper-3.4.6/conf/
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg
修改以下內容
# The number of milliseconds of each tick
# 這個時間是zookeeper服務器之間或者服務器與客戶端之間維持心跳的時間機制
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
# 配置zookeeper接收客戶端初始化連接時最長能忍受多少個心跳時間間隔數
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
# leader和follower之間發送訊息,請求和應答時間長度
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 資料目錄,需要提前創建
dataDir=/export/servers/zookeeper-3.4.6/zkdatas
# 日志目錄,也需要提前創建
dataLogDir=/export/servers/zookeeper-3.4.6/zkLogs
# 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
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# 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
# 設定清除任務間隔
autopurge.purgeInterval=1
# zookeeper cluster properties
# 設定每個節點上的服務
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888
4. 第四步:添加myid配置
在node1主機的/export/server/zookeeper-3.4.6/zkdatas/這個路徑下創建一個檔案,檔案名為myid ,檔案內容為1
echo 1 > /export/servers/zookeeper-3.4.6/zkdatas/myid
5. 第五步:安裝包分發并修改myid的值
在node1主機上,將安裝包分發到其他機器
第一臺機器上面執行以下兩個命令
scp -r /export/servers/zookeeper-3.4.6 node2:$PWD
scp -r /export/servers/zookeeper-3.4.6 node3:$PWD
第二臺機器上修改myid的值為2
echo 2 > /export/servers/zookeeper-3.4.6/zkdatas/myid
第三臺機器上修改myid的值為3
echo 3 > /export/servers/zookeeper-3.4.6/zkdatas/myid
6. 第六步:三臺機器啟動zookeeper服務
三臺機器分別啟動zookeeper服務
這個命令三臺機器都要執行
/export/servers/zookeeper-3.4.6/bin/zkServer.sh start
三臺主機分別查看啟動狀態
/export/servers/zookeeper-3.4.6/bin/zkServer.sh status



3:zookeeper資料模型:
zookeeper維護這一個樹狀層次結構,樹中的節點被稱之為znode,znode可以用來存盤資料,并且有一個與之相關聯的ACL(Access Control List:訪問控制串列,用于控制資源的訪問權限),zookeeper被設計用來實作協調服務(通常使用小資料檔案),而不適用于大容量資料存盤,因此一個znode能存盤的資料被限制在1MB以內,znode的樹狀結構如下圖所示:

圖中的每個節點稱為一個Znode, 每個Znode由3部分組成:
ZooKeeper的資料模型,在結構上和標準檔案系統的非常相似,擁有一個層次的命名空間,都是采用樹形層次結構,ZooKeeper樹中的每個節點被稱為—Znode,和檔案系統的目錄樹一樣,ZooKeeper樹中的每個節點可以擁有子節點,但也有不同之處:
-
Znode兼具檔案和目錄兩種特點,既像檔案一樣維護著資料、元資訊、ACL、時間戳等資料結構,又像目錄一樣可以作為路徑標識的一部分,并可以具有子znode,用戶對znode具有增、刪、改、查等操作(權限允許的情況下),
-
Znode具有原子性操作,讀操作將獲取與節點相關的所有資料,寫操作也將替換掉節點的所有資料,另外,每一個節點都擁有自己的ACL(訪問控制串列),這個串列規定了用戶的權限,即限定了特定用戶對目標節點可以執行的操作,
-
Znode存盤資料大小有限制,ZooKeeper雖然可以關聯一些資料,但并沒有被設計為常規的資料庫或者大資料存盤,相反的是,它用來管理調度資料,比如分布式應用中的組態檔資訊、狀態資訊、匯集位置等等,這些資料的共同特性就是它們都是很小的資料,通常以KB為大小單位,ZooKeeper的服務器和客戶端都被設計為嚴格檢查并限制每個Znode的資料大小至多1M,當時常規使用中應該遠小于此值,
-
Znode通過路徑參考,如同Unix中的檔案路徑,路徑必須是絕對的,因此他們必須由斜杠字符來開頭,除此以外,他們必須是唯一的,也就是說每一個路徑只有一個表示,因此這些路徑不能改變,在ZooKeeper中,路徑由Unicode字串組成,并且有一些限制,字串"/zookeeper"用以保存管理資訊,比如關鍵配額資訊,
① stat:此為狀態資訊, 描述該Znode的版本, 權限等資訊
② data:與該Znode關聯的資料
③ children:該Znode下的子節點znode是客戶端訪問的zookeeper的主要物體,
4、Zookeeper節點型別:
Znode有兩種,分別為臨時節點和永久節點,
節點的型別在創建時即被確定,并且不能改變,
- 臨時節點:該節點的生命周期依賴于創建它們的會話,一旦會話結束,臨時節點將被自動洗掉,當然可以也可以手動洗掉,臨時節點不允許擁有子節點,
- 永久節點:該節點的生命周期不依賴于會話,并且只有在客戶端顯示執行洗掉操作的時候,他們才能被洗掉,
Znode還有一個序列化的特性,如果創建的時候指定的話,該Znode的名字后面會自動追加一個不斷增加的序列號,序列號對于此節點的父節點來說是唯一的,這樣便會記錄每個子節點創建的先后順序,它的格式為“%10d”(10位數字,沒有數值的數位用0補充,例如“0000000001”),

這樣便會存在四種型別的Znode節點,分別對應:
PERSISTENT:永久節點
EPHEMERAL:臨時節點
PERSISTENT_SEQUENTIAL:永久節點、序列化
EPHEMERAL_SEQUENTIAL:臨時節點、序列化
5:zookeeper Watcher (監聽機制)
ZooKeeper提供了分布式資料發布/訂閱功能,一個典型的發布/訂閱模型系統定義了一種一對多的訂閱關系,能讓多個訂閱者同時監聽某一個主題物件,當這個主題物件自身狀態變化時,會通知所有訂閱者,使他們能夠做出相應的處理,
ZooKeeper中,引入了Watcher機制來實作這種分布式的通知功能,ZooKeeper允許客戶端向服務端注冊一個Watcher監聽,當服務端的一些事件觸發了這個Watcher,那么就會向指定客戶端發送一個事件通知來實作分布式的通知功能,
觸發事件種類很多,如:節點創建,節點洗掉,節點改變,子節點改變等,
總的來說可以概括Watcher為以下三個程序:客戶端向服務端注冊Watcher、服務端事件發生觸發Watcher、客戶端回呼Watcher得到觸發事件情況
1) Watch機制特點
一次性觸發
事件發生觸發監聽,一個watcher event就會被發送到設定監聽的客戶端,這種效果是一次性的,后續再次發生同樣的事件,不會再次觸發,
事件封裝
ZooKeeper使用WatchedEvent物件來封裝服務端事件并傳遞,
WatchedEvent包含了每一個事件的三個基本屬性:
通知狀態(keeperState),事件型別(EventType)和節點路徑(path)
event 異步發送
watcher的通知事件從服務端發送到客戶端是異步的,
先注冊再觸發
Zookeeper中的watch機制,必須客戶端先去服務端注冊監聽,這樣事件發送才會觸發監聽,通知給客戶端,
2) 通知狀態和事件型別
同一個事件型別在不同的通知狀態中代表的含義有所不同,下表列舉了常見的通知狀態和事件型別,
事件封裝: Watcher 得到的事件是被封裝過的, 包括三個內容 keeperState, eventType, path
| KeeperState (通知狀態) | EventType (事件型別) | 觸發條件 | 說明 |
|---|---|---|---|
| None | 連接成功 | ||
| SyncConnected | NodeCreated | Znode被創建 | 此時處于連接狀態 |
| SyncConnected | NodeDeleted | Znode被洗掉 | 此時處于連接狀態 |
| SyncConnected | NodeDataChanged | Znode資料被改變 | 此時處于連接狀態 |
| SyncConnected | NodeChildChanged | Znode的子Znode資料被改變 | 此時處于連接狀態 |
| Disconnected | None | 客戶端和服務端斷開連接 | 此時客戶端和服務器處于斷開連接狀態 |
| Expired | None | 會話超時 | 會收到一個SessionExpiredExceptio |
| AuthFailed | None | 權限驗證失敗 | 會收到一個AuthFailedException |
其中連接狀態事件(type=None, path=null)不需要客戶端注冊,客戶端只要有需要直接處理就行了,
3) Shell 客戶端設定watcher
設定節點資料變動監聽:

通過另一個客戶端更改節點資料:

此時設定監聽的節點收到通知:

6、ZooKeeper的shell操作
1. 客戶端連接
運行 zkCli.sh –server ip 進入命令列工具,
cd /export/servers/zookeeper-3.4.6/bin
./zkCli.sh -server node1:2181
2. shell基本操作
1) 創建節點:
格式:
create [-s][-e] path data acl
其中,-s 表示創建一個序列化節點
? -e 表示創建一個臨時節點
? 啥也不加表示創建了一個永久節點
? acl:權限控制
創建永久節點:
create /aaa 123456
創建臨時節點
create -e /linshijiedian 123456
創建永久序列化節點
create -s /abc 123
創建臨時序列化節點
create -s -e /fff 123345
2) 讀取節點
與讀取相關的命令有 ls 命令和 get 命令,ls和ls2 命令可以列出 Zookeeper 指定節點下的所有子節點,只能查看指定節點下的第一級的所有子節點;get 命令可以獲取 Zookeeper 指定節點的資料內容和屬性資訊,
格式:
ls path [watch]
get path [watch]
ls2 path [watch]
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RcgLN6PI-1641199684803)(http://www.honghuboyuan.top//image-20211212141158612.png)]](https://img.uj5u.com/2022/01/05/2950610521053316.png)
dataVersion:資料版本號,每次對節點進行set操作,dataVersion的值都會增加1(即使設定的是相同的資料),可有效避免了資料更新時出現的先后順序問題,
cversion :子節點的版本號,當znode的子節點有變化時,cversion 的值就會增加1,
cZxid :Znode創建的事務id,
mZxid :Znode被修改的事務id,即每次對znode的修改都會更新mZxid,
對于zk來說,每次的變化都會產生一個唯一的事務id,zxid(ZooKeeper Transaction Id),通過zxid,可以確定更新操作的先后順序,例如,如果zxid1小于zxid2,說明zxid1操作先于zxid2發生,zxid對于整個zk都是唯一的,即使操作的是不同的znode,
ctime:節點創建時的時間戳.
mtime:節點最新一次更新發生時的時間戳.
ephemeralOwner:如果該節點為臨時節點, ephemeralOwner值表示與該節點系結的session id. 如果不是, ephemeralOwner值為0.
在client和server通信之前,首先需要建立連接,該連接稱為session,連接建立后,如果發生連接超時、授權失敗,或者顯式關閉連接,連接便處于CLOSED狀態, 此時session結束,
3) 更新索引
格式:
set path data [version]
data 就是要更新的新內容,version 表示資料版本

4) 洗掉節點
格式:
delete path [version]
若洗掉節點存在子節點,那么無法洗掉該節點,必須先洗掉子節點,再洗掉父節點
rmr path: 可以遞回洗掉節點,
5) 對節點進行限制: quota
格式1:
setquota -n|-b val path
-n:表示子節點的最大個數
-b:表示資料值的最大長度
val:子節點最大個數或資料值的最大長度
path:節點路徑
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vhGovpl9-1641199684806)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20211212143643334.png)]](https://img.uj5u.com/2022/01/05/2950610521053310.png)
格式2: listquota path : 列出指定節點的 quota
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-deOayTfA-1641199684808)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20211212143652474.png)]](https://img.uj5u.com/2022/01/05/2950610521053311.png)
子節點個數為 2,資料長度-1 表示沒限制
注意: 在實際操作的時候, 雖然設定了最大的節點數后,依然可以在整個節點下添加多個子節點, 只是會在zookeeper中的日志檔案中記錄一下警告資訊![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UEvy8xOK-1641199684810)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20211212143708410.png)]](https://img.uj5u.com/2022/01/05/2950610521053312.png)
格式3: delquota [-n|-b] path : 洗掉 quota
6) 其他命令:
history: 列出命令歷史

redo:該命令可以重新執行指定命令編號的歷史命令,命令編號可以通過
7、ZooKeeper Java API操作
這里操作Zookeeper的JavaAPI使用的是一套zookeeper客戶端框架 Curator ,解決了很多Zookeeper客戶端非常底層的細節開發作業 ,
Curator包含了幾個包:
curator-framework:對zookeeper的底層api的一些封裝
curator-recipes:封裝了一些高級特性,如:Cache事件監聽、選舉、分布式鎖、分布式計數器等
Maven依賴(使用curator的版本:2.12.0,對應Zookeeper的版本為:3.4.x,如果跨版本會有兼容性問題,很有可能導致節點操作失敗):
1. 引入maven坐標
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java編譯插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
java 操作zookeeper的相關操作:
package com.lixufei.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.junit.Test;
/**
* @Author lixufei
* @Date 2021/12/12 15:03
* @Version 1.0
*/
public class ZookeeperTest{
/**
* 使用java操縱zookeeper,創建節點
*/
@Test
public void createZnode() throws Exception {
// 創建java操作zookeeper的客戶端物件
String connectString = "192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181";
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework zookeeperClient = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
// 啟動客戶端
zookeeperClient.start();
// 執行相關的操作
/**
* CreateMode.PERSISTENT:永久節點
* CreateMode.PERSISTENT_SEQUENTIAL:永久序列化節點
* CreateMode.EPHEMERAL:臨時節點
* CreateMode.EPHEMERAL_SEQUENTIAL:臨時序列化節點
*/
zookeeperClient.create().withMode(CreateMode.PERSISTENT).forPath("/test04","123456".getBytes());
// 釋放資源
zookeeperClient.close();
}
/**
* 使用java操縱zookeeper,修改節點
*/
@Test
public void updataZnode() throws Exception {
// 創建java操作zookeeper的客戶端物件
String connectString = "192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181";
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework zookeeperClient = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
// 啟動客戶端
zookeeperClient.start();
// 執行相關的操作
zookeeperClient.setData().forPath("/test03","lixufei123456".getBytes());
// 釋放資源
zookeeperClient.close();
}
/**
* 使用java操縱zookeeper,洗掉節點
*/
@Test
public void deleteZnode() throws Exception {
// 創建java操作zookeeper的客戶端物件
String connectString = "192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181";
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework zookeeperClient = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
// 啟動客戶端
zookeeperClient.start();
// 執行相關的操作
zookeeperClient.delete().forPath("/test04");
// 釋放資源
zookeeperClient.close();
}
/**
* 使用java操縱zookeeper,查詢節點
*/
@Test
public void selectZnode() throws Exception {
// 創建java操作zookeeper的客戶端物件
String connectString = "192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181";
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework zookeeperClient = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
// 啟動客戶端
zookeeperClient.start();
// 執行相關的操作
byte[] bytes = zookeeperClient.getData().forPath("/test03");
System.out.println(new String(bytes));
// 釋放資源
zookeeperClient.close();
}
}
8、ZooKeeper選舉機制
zookeeper默認的演算法是FastLeaderElection,采用投票數大于半數則勝出的邏輯,
1. 概念
服務器ID
比如有三臺服務器,編號分別是1,2,3,
編號越大在選擇演算法中的權重越大,
選舉狀態
LOOKING,競選狀態,
FOLLOWING,隨從狀態,同步leader狀態,參與投票,
OBSERVING,觀察狀態,同步leader狀態,不參與投票,
LEADING,領導者狀態,
資料ID
服務器中存放的最新資料version,
值越大說明資料越新,在選舉演算法中資料越新權重越大,
邏輯時鐘
也叫投票的次數,同一輪投票程序中的邏輯時鐘值是相同的,每投完一次票這個資料就會增加,然后與接收到的其它服務器回傳的投票資訊中的數值相比,根據不同的值做出不同的判斷,
2. 全新集群選舉
假設目前有5臺服務器,每臺服務器均沒有資料,它們的編號分別是1,2,3,4,5,按編號依次啟動,它們的選擇舉程序如下:
服務器1啟動自己,給投票,然后發投票資訊,由于其它機器還沒有啟動所以它收不到反饋資訊,服務器1的狀態一直屬于Looking,
服務器2啟動,給自己投票,同時與之前啟動的服務器1交換結果,由于服務器2的編號大所以服務器2勝出,但此時投票數沒有大于半數,所以兩個服務器的狀態依然是LOOKING,
服務器3啟動,給自己投票,同時與之前啟動的服務器1,2交換資訊,由于服務器3的編號最大所以服務器3勝出,此時投票數正好大于半數,所以服務器3成為領導者,服務器1,2成為小弟,
服務器4啟動,給自己投票,同時與之前啟動的服務器1,2,3交換資訊,盡管服務器4的編號大,但之前服務器3已經勝出,所以服務器4只能成為小弟,
服務器5啟動,后面的邏輯同服務器4成為小弟,
3. 非全新集群選舉
對于運行正常的zookeeper集群,中途有機器down掉,需要重新選舉時,選舉程序就需要加入資料ID、服務器ID和邏輯時鐘,
資料ID:資料新的version就大,資料每次更新都會更新version,
服務器ID:就是我們配置的myid中的值,每個機器一個,
邏輯時鐘:這個值從0開始遞增,每次選舉對應一個值, 如果在同一次選舉中,這個值是一致的,
這樣選舉的標準就變成:
? 1、邏輯時鐘小的選舉結果被忽略,重新投票;
? 2、統一邏輯時鐘后,資料id大的勝出;
? 3、資料id相同的情況下,服務器id大的勝出;
根據這個規則選出leader,
9、ZAB協議
4.1 ZAB協議與資料一致性
ZAB 協議是 Zookeeper 專門設計的一種支持崩潰恢復的原子廣播協議,通過該協議,Zookeepe 基于主從模式的系統架構來保持集群中各個副本之間資料的一致性,具體如下:
Zookeeper 使用一個單一的主行程來接收并處理客戶端的所有事務請求,并采用原子廣播協議將資料狀態的變更以事務 Proposal 的形式廣播到所有的副本行程上去,如下圖:

具體流程如下:
所有的事務請求必須由唯一的 Leader 服務來處理,Leader 服務將事務請求轉換為事務 Proposal,并將該 Proposal 分發給集群中所有的 Follower 服務,如果有半數的 Follower 服務進行了正確的反饋,那么 Leader 就會再次向所有的 Follower 發出 Commit 訊息,要求將前一個 Proposal 進行提交,
4.2 ZAB協議的內容
ZAB 協議包括兩種基本的模式,分別是崩潰恢復和訊息廣播:
1. 崩潰恢復
當整個服務框架在啟動程序中,或者當 Leader 服務器出現例外時,ZAB 協議就會進入恢復模式,通過過半選舉機制產生新的 Leader,之后其他機器將從新的 Leader 上同步狀態,當有過半機器完成狀態同步后,就退出恢復模式,進入訊息廣播模式,
2. 訊息廣播
ZAB 協議的訊息廣播程序使用的是原子廣播協議,在整個訊息的廣播程序中,Leader 服務器會每個事物請求生成對應的 Proposal,并為其分配一個全域唯一的遞增的事務 ID(ZXID),之后再對其進行廣播,具體程序如下:
Leader 服務會為每一個 Follower 服務器分配一個單獨的佇列,然后將事務 Proposal 依次放入佇列中,并根據 FIFO(先進先出) 的策略進行訊息發送,Follower 服務在接收到 Proposal 后,會將其以事務日志的形式寫入本地磁盤中,并在寫入成功后反饋給 Leader 一個 Ack 回應,當 Leader 接收到超過半數 Follower 的 Ack 回應后,就會廣播一個 Commit 訊息給所有的 Follower 以通知其進行事務提交,之后 Leader 自身也會完成對事務的提交,而每一個 Follower 則在接收到 Commit 訊息后,完成事務的提交,

10、Zookeeper的典型應用場景
5.1資料的發布/訂閱
資料的發布/訂閱系統,通常也用作配置中心,在分布式系統中,你可能有成千上萬個服務節點,如果想要對所有服務的某項配置進行更改,由于資料節點過多,你不可逐臺進行修改,而應該在設計時采用統一的配置中心,之后發布者只需要將新的配置發送到配置中心,所有服務節點即可自動下載并進行更新,從而實作配置的集中管理和動態更新,
Zookeeper 通過 Watcher 機制可以實作資料的發布和訂閱,分布式系統的所有的服務節點可以對某個 ZNode 注冊監聽,之后只需要將新的配置寫入該 ZNode,所有服務節點都會收到該事件,
5.2 命名服務
在分布式系統中,通常需要一個全域唯一的名字,如生成全域唯一的訂單號等,Zookeeper 可以通過順序節點的特性來生成全域唯一 ID,從而可以對分布式系統提供命名服務,
5.3 Master選舉
分布式系統一個重要的模式就是主從模式 (Master/Salves),Zookeeper 可以用于該模式下的 Matser 選舉,可以讓所有服務節點去競爭性地創建同一個 ZNode,由于 Zookeeper 不能有路徑相同的 ZNode,必然只有一個服務節點能夠創建成功,這樣該服務節點就可以成為 Master 節點,
5.4 分布式鎖
可以通過 Zookeeper 的臨時節點和 Watcher 機制來實作分布式鎖,這里以排它鎖為例進行說明:
分布式系統的所有服務節點可以競爭性地去創建同一個臨時 ZNode,由于 Zookeeper 不能有路徑相同的 ZNode,必然只有一個服務節點能夠創建成功,此時可以認為該節點獲得了鎖,其他沒有獲得鎖的服務節點通過在該 ZNode 上注冊監聽,從而當鎖釋放時再去競爭獲得鎖,鎖的釋放情況有以下兩種:
- 當正常執行完業務邏輯后,客戶端主動將臨時 ZNode 洗掉,此時鎖被釋放;
- 當獲得鎖的客戶端發生宕機時,臨時 ZNode 會被自動洗掉,此時認為鎖已經釋放,
當鎖被釋放后,其他服務節點則再次去競爭性地進行創建,但每次都只有一個服務節點能夠獲取到鎖,這就是排他鎖,
5.5 集群管理
Zookeeper 還能解決大多數分布式系統中的問題:
- 如可以通過創建臨時節點來建立心跳檢測機制,如果分布式系統的某個服務節點宕機了,則其持有的會話會超時,此時該臨時節點會被洗掉,相應的監聽事件就會被觸發,
- 分布式系統的每個服務節點還可以將自己的節點狀態寫入臨時節點,從而完成狀態報告或節點作業進度匯報,
- 通過資料的訂閱和發布功能,Zookeeper 還能對分布式系統進行模塊的解耦和任務的調度,
- 通過監聽機制,還能對分布式系統的服務節點進行動態上下線,從而實作服務的動態擴容,
11:Zookeeper ACL
1:前言:
為了避免存盤在 Zookeeper 上的資料被其他程式或者人為誤修改,Zookeeper 提供了 ACL(Access Control Lists) 進行權限控制,只有擁有對應權限的用戶才可以對節點進行增刪改查等操作,下文分別介紹使用原生的 Shell 命令和 Apache Curator 客戶端進行權限設定,
二、使用Shell進行權限管理
2.1 設定與查看權限
想要給某個節點設定權限 (ACL),有以下兩個可選的命令:
# 1.給已有節點賦予權限
setAcl path acl
# 2.在創建節點時候指定權限
create [-s] [-e] path data acl
查看指定節點的權限命令如下:
getAcl path
2.2 權限組成
Zookeeper 的權限由[scheme : id :permissions]三部分組成,其中 Schemes 和 Permissions 內置的可選項分別如下:
Permissions 可選項:
- CREATE:允許創建子節點;
- READ:允許從節點獲取資料并列出其子節點;
- WRITE:允許為節點設定資料;
- DELETE:允許洗掉子節點;
- ADMIN:允許為節點設定權限,
Schemes 可選項:
- world:默認模式,所有客戶端都擁有指定的權限,world 下只有一個 id 選項,就是 anyone,通常組合寫法為
world:anyone:[permissons]; - auth:只有經過認證的用戶才擁有指定的權限,通常組合寫法為
auth:user:password:[permissons],使用這種模式時,你需要先進行登錄,之后采用 auth 模式設定權限時,user和password都將使用登錄的用戶名和密碼; - digest:只有經過認證的用戶才擁有指定的權限,通常組合寫法為
auth:user:BASE64(SHA1(password)):[permissons],這種形式下的密碼必須通過 SHA1 和 BASE64 進行雙重加密; - ip:限制只有特定 IP 的客戶端才擁有指定的權限,通常組成寫法為
ip:182.168.0.168:[permissions]; - super:代表超級管理員,擁有所有的權限,需要修改 Zookeeper 啟動腳本進行配置,
2.3 添加認證資訊
可以使用如下所示的命令為當前 Session 添加用戶認證資訊,等價于登錄操作,
# 格式
addauth scheme auth
#示例:添加用戶名為heibai,密碼為root的用戶認證資訊
addauth digest heibai:root
2.4 權限設定示例
1. world模式
world 是一種默認的模式,即創建時如果不指定權限,則默認的權限就是 world,
[zk: localhost:2181(CONNECTED) 32] create /hadoop 123
Created /hadoop
[zk: localhost:2181(CONNECTED) 33] getAcl /hadoop
'world,'anyone #默認的權限
: cdrwa
[zk: localhost:2181(CONNECTED) 34] setAcl /hadoop world:anyone:cwda # 修改節點,不允許所有客戶端讀
....
[zk: localhost:2181(CONNECTED) 35] get /hadoop
Authentication is not valid : /hadoop # 權限不足
2. auth模式
[zk: localhost:2181(CONNECTED) 36] addauth digest heibai:heibai # 登錄
[zk: localhost:2181(CONNECTED) 37] setAcl /hadoop auth::cdrwa # 設定權限
[zk: localhost:2181(CONNECTED) 38] getAcl /hadoop # 獲取權限
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #用戶名和密碼 (密碼經過加密處理),注意回傳的權限型別是 digest
: cdrwa
#用戶名和密碼都是使用登錄的用戶名和密碼,即使你在創建權限時候進行指定也是無效的
[zk: localhost:2181(CONNECTED) 39] setAcl /hadoop auth:root:root:cdrwa #指定用戶名和密碼為 root
[zk: localhost:2181(CONNECTED) 40] getAcl /hadoop
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #無效,使用的用戶名和密碼依然還是 heibai
: cdrwa
3. digest模式
[zk:44] create /spark "spark" digest:heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s=:cdrwa #指定用戶名和加密后的密碼
[zk:45] getAcl /spark #獲取權限
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= # 回傳的權限型別是 digest
: cdrwa
到這里你可以發現使用 auth 模式設定的權限和使用 digest 模式設定的權限,在最終結果上,得到的權限模式都是 digest,某種程度上,你可以把 auth 模式理解成是 digest 模式的一種簡便實作,因為在 digest 模式下,每次設定都需要書寫用戶名和加密后的密碼,這是比較繁瑣的,采用 auth 模式就可以避免這種麻煩,
4. ip模式
限定只有特定的 ip 才能訪問,
[zk: localhost:2181(CONNECTED) 46] create /hive "hive" ip:192.168.0.108:cdrwa
[zk: localhost:2181(CONNECTED) 47] get /hive
Authentication is not valid : /hive # 當前主機已經不能訪問
這里可以看到當前主機已經不能訪問,想要能夠再次訪問,可以使用對應 IP 的客戶端,或使用下面介紹的 super 模式,
5. super模式
需要修改啟動腳本 zkServer.sh,并在指定位置添加超級管理員賬戶和密碼資訊:
"-Dzookeeper.DigestAuthenticationProvider.superDigest=heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s="

修改完成后需要使用 zkServer.sh restart 重啟服務,此時再次訪問限制 IP 的節點:
[zk: localhost:2181(CONNECTED) 0] get /hive #訪問受限
Authentication is not valid : /hive
[zk: localhost:2181(CONNECTED) 1] addauth digest heibai:heibai # 登錄 (添加認證資訊)
[zk: localhost:2181(CONNECTED) 2] get /hive #成功訪問
hive
cZxid = 0x158
ctime = Sat May 25 09:11:29 CST 2019
mZxid = 0x158
mtime = Sat May 25 09:11:29 CST 2019
pZxid = 0x158
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
三、使用Java客戶端進行權限管理
3.1 主要依賴
這里以 Apache Curator 為例,使用前需要匯入相關依賴,完整依賴如下:
<dependencies>
<!--Apache Curator 相關依賴-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<!--單元測驗相關依賴-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
3.2 權限管理API
Apache Curator 權限設定的示例如下:
public class AclOperation {
private CuratorFramework client = null;
private static final String zkServerPath = "192.168.0.226:2181";
private static final String nodePath = "/hadoop/hdfs";
@Before
public void prepare() {
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder()
.authorization("digest", "heibai:123456".getBytes()) //等價于 addauth 命令
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("workspace").build();
client.start();
}
/**
* 新建節點并賦予權限
*/
@Test
public void createNodesWithAcl() throws Exception {
List<ACL> aclList = new ArrayList<>();
// 對密碼進行加密
String digest1 = DigestAuthenticationProvider.generateDigest("heibai:123456");
String digest2 = DigestAuthenticationProvider.generateDigest("ying:123456");
Id user01 = new Id("digest", digest1);
Id user02 = new Id("digest", digest2);
// 指定所有權限
aclList.add(new ACL(Perms.ALL, user01));
// 如果想要指定權限的組合,中間需要使用 | ,這里的|代表的是位運算中的 按位或
aclList.add(new ACL(Perms.DELETE | Perms.CREATE, user02));
// 創建節點
byte[] data = "abc".getBytes();
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.withACL(aclList, true)
.forPath(nodePath, data);
}
/**
* 給已有節點設定權限,注意這會洗掉所有原來節點上已有的權限設定
*/
@Test
public void SetAcl() throws Exception {
String digest = DigestAuthenticationProvider.generateDigest("admin:admin");
Id user = new Id("digest", digest);
client.setACL()
.withACL(Collections.singletonList(new ACL(Perms.READ | Perms.DELETE, user)))
.forPath(nodePath);
}
/**
* 獲取權限
*/
@Test
public void getAcl() throws Exception {
List<ACL> aclList = client.getACL().forPath(nodePath);
ACL acl = aclList.get(0);
System.out.println(acl.getId().getId()
+ "是否有刪讀權限:" + (acl.getPerms() == (Perms.READ | Perms.DELETE)));
}
@After
public void destroy() {
if (client != null) {
client.close();
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/403916.html
標籤:其他
下一篇:Kimball維度建模
