ZooKeeper 介紹與使用


1. zookeeper是什么?

ZooKeeper是一個分布式的,開放源碼的分布式應用程序協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要組件。它是一個為分布式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分布式同步、組服務等。

2. zookeeper能做什么?

Apache ZooKeeper是由集群(節點組)使用的一種服務,用於在自身之間協調,並通過穩健的同步技術維護共享數據。ZooKeeper本身是一個分布式應用程序,為寫入分布式應用程序提供服務。

zooKeeper提供的常見服務如下 :

  • 命名服務 - 按名稱標識集群中的節點。它類似於DNS,但僅對於節點。

  • 配置管理 - 加入節點的最近的和最新的系統配置信息。

  • 集群管理 - 實時地在集群和節點狀態中加入/離開節點。

  • 選舉算法 - 選舉一個節點作為協調目的的leader。

  • 鎖定和同步服務 - 在修改數據的同時鎖定數據。此機制可幫助你在連接其他分布式應用程序(如Apache HBase)時進行自動故障恢復。

  • 高度可靠的數據注冊表 - 即使在一個或幾個節點關閉時也可以獲得數據。

分布式應用程序提供了很多好處,但它們也拋出了一些復雜和難以解決的挑戰。ZooKeeper框架提供了一個完整的機制來克服所有的挑戰。競爭條件和死鎖使用故障安全同步方法進行處理。另一個主要缺點是數據的不一致性,ZooKeeper使用原子性解析。

zooKeeper的好處

以下是使用zooKeeper的好處:

  • 簡單的分布式協調過程
  • 同步 - 服務器進程之間的相互排斥和協作。此過程有助於Apache HBase進行配置管理。
  • 有序的消息
  • 序列化 - 根據特定規則對數據進行編碼。確保應用程序運行一致。這種方法可以在MapReduce中用來協調隊列以執行運行的線程。
  • 可靠性
  • 原子性 - 數據轉移完全成功或完全失敗,但沒有事務是部分的。

3. zookeeper 基礎

zooKeeper的架構

zooKeeper的架構
作為ZooKeeper架構的一部分的每個組件在下表中進行了說明。

部分 描述
Client(客戶端) 客戶端,我們的分布式應用集群中的一個節點,從服務器訪問信息。對於特定的時間間隔,每個客戶端向服務器發送消息以使服務器知道客戶端是活躍的。類似地,當客戶端連接時,服務器發送確認碼。如果連接的服務器沒有響應,客戶端會自動將消息重定向到另一個服務器。
Server(服務器) 服務器,我們的ZooKeeper總體中的一個節點,為客戶端提供所有的服務。向客戶端發送確認碼以告知服務器是活躍的。
Ensemble ZooKeeper服務器組。形成ensemble所需的最小節點數為3。
Leader 服務器節點,如果任何連接的節點失敗,則執行自動恢復。Leader在服務啟動時被選舉。
Follower 跟隨leader指令的服務器節點。

層次命名空間

下圖描述了用於內存表示的ZooKeeper文件系統的樹結構。ZooKeeper節點稱為 znode 。每個znode由一個名稱標識,並用路徑(/)序列分隔。

  • 在圖中,首先有一個由“/”分隔的znode。在根目錄下,你有兩個邏輯命名空間 config 和 workers 。
  • config 命名空間用於集中式配置管理,workers 命名空間用於命名。
  • 在 config 命名空間下,每個znode最多可存儲1MB的數據。這與UNIX文件系統相類似,除了父znode也可以存儲數據。這種結構的主要目的是存儲同步數據並描述znode的元數據。此結構稱為 ZooKeeper數據模型。

Znode兼具文件和目錄兩種特點。既像文件一樣維護着數據長度、元信息、ACL、時間戳等數據結構,又像目錄一樣可以作為路徑標識的一部分。每個Znode由三個部分組成:

  • stat:此為狀態信息,描述該Znode版本、權限等信息。
  • data:與該Znode關聯的數據
  • children:該Znode下的節點
     
  • 版本號 - 每個znode都有版本號,這意味着每當與znode相關聯的數據發生變化時,其對應的版本號也會增加。當多個zookeeper客戶端嘗試在同一znode上執行操作時,版本號的使用就很重要。
  • 操作控制列表(ACL)- ACL基本上是訪問znode的認證機制。它管理所有znode讀取和寫入操作。
  • 時間戳 - 時間戳表示創建和修改znode所經過的時間。它通常以毫秒為單位。ZooKeeper從“事務ID"(zxid)標識znode的每個更改。Zxid 是唯一的,並且為每個事務保留時間,以便你可以輕松地確定從一個請求到另一個請求所經過的時間。
  • 數據長度 - 存儲在znode中的數據總量是數據長度。你最多可以存儲1MB的數據。

Znode的類型

Znode被分為持久(persistent)節點,順序(sequential)節點和臨時(ephemeral)節點。

  • 持久節點 - 即使在創建該特定znode的客戶端斷開連接后,持久節點仍然存在。默認情況下,除非另有說明,否則所有znode都是持久的。
  • 臨時節點 - 客戶端活躍時,臨時節點就是有效的。當客戶端與ZooKeeper集合斷開連接時,臨時節點會自動刪除。因此,只有臨時節點不允許有子節點。如果臨時節點被刪除,則下一個合適的節點將填充其位置。臨時節點在leader選舉中起着重要作用。
  • 順序節點 - 順序節點可以是持久的或臨時的。當一個新的znode被創建為一個順序節點時,ZooKeeper通過將10位的序列號附加到原始名稱來設置znode的路徑。例如,如果將具有路徑 /myapp 的znode創建為順序節點,則ZooKeeper會將路徑更改為 /myapp0000000001 ,並將下一個序列號設置為0000000002。如果兩個順序節點是同時創建的,那么ZooKeeper不會對每個znode使用相同的數字。順序節點在鎖定和同步中起重要作用。
  • 容器節點(3.6新增的):ZooKeeper具有容器節點的概念。容器節點是特殊用途的節點,可用於諸如集群中的leader,分布式鎖等場景。當刪除容器節點的最后一個子節點時,也就是容器節點沒有子節點之后,該容器節點會在未來某個時間被服務器刪除。
  • TTL節點(3.6新增的):在創建持久化或者持久化有序節點時,可以把節點設置為TTL節點。並指定TTL的時間,單位為毫秒。如果接在在TTL時間內未修改且沒有子代,該節點會在未來某個時間被服務器刪除。該屬性默認是禁用的,需要額外配置才能啟用。

Sessions(會話)

會話對於ZooKeeper的操作非常重要。會話中的請求按FIFO順序執行。一旦客戶端連接到服務器,將建立會話並向客戶端分配會話ID 。
客戶端以特定的時間間隔發送心跳以保持會話有效。如果ZooKeeper集合在超過服務器開啟時指定的期間(會話超時)都沒有從客戶端接收到心跳,則它會判定客戶端死機。
會話超時通常以毫秒為單位。當會話由於任何原因結束時,在該會話期間創建的臨時節點也會被刪除。

Watches(監視)

監視是一種簡單的機制,使客戶端收到關於ZooKeeper集合中的更改的通知。客戶端可以在讀取特定znode時設置Watches。Watches會向注冊的客戶端發送任何znode(客戶端注冊表)更改的通知。
Znode更改是與znode相關的數據的修改或znode的子項中的更改。只觸發一次watches。如果客戶端想要再次通知,則必須通過另一個讀取操作來完成。當連接會話過期時,客戶端將與服務器斷開連接,相關的watches也將被刪除。

4. zookeeper 安裝

4.1 單機安裝

4.1.1 安裝JDK

ZooKeeper服務器是用Java創建的,它在JVM上運行。你需要使用JDK 6或更高版本。linux安裝jdk8

# 查看jdk版本
java -version

4.1.2 下載安裝zookeeper

# 下載對應版本安裝包
wget http://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz

# 解壓到指定安裝目錄
tar -zxvf apache-zookeeper-3.6.2-bin.tar.gz -C /usr/local/zookeeper

# 創建數據和日志目錄
cd /usr/local/zookeeper && mv apache-zookeeper-3.6.2-bin apache-zookeeper-3.6.2
mkdir -pv data logs

# 設置自己的配置文件
vi /usr/local/zookeeper/apache-zookeeper-3.6.2/conf/zoo.cfg

4.1.3 zookeeper配置文件

# 心跳間隔 毫秒
tickTime=1000

# 啟動時leader連接follower,超過多少次心跳間隔,follower連接超時
initLimit=10
# leader與follower 通信超時 最長心跳間隔次數
syncLimit=5

# 客戶端連接的端口
clientPort=2181
# 客戶端連接的最大數量
#maxClientCnxns=60

# 數據文件目錄
dataDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/data
# 日志目錄
dataLogDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/logs

# 最小會話超時時間
# minSessionTimeout=2000
# 最大會話超時時間
# maxSessionTimeout=20000

# 清除任務間隔(以小時為單位)
# 設置為0以禁用自動清除功能
autopurge.purgeInterval=5
# 最多保存20個文件 日志文件、快照
autopurge.snapRetainCount=20 

#開啟四字命令
#4lw.commands.whitelist=*

# 集群信息
# server.A=B:C:D
# A 代表記號服務器,在dataDir目錄下myid文件下記錄
# B 服務器ip地址
# C 服務器與集群中的leader服務器交換信息的端口
# D 選舉leader所用的端口
# server.1=127.0.0.1:3181:4181

4.1.4 常用shell操作命令

  • 啟動zk : bin/zkServer.sh start
  • 查看ZK服務狀態: bin/zkServer.sh status
  • 停止ZK服務: bin/zkServer.sh stop
  • 重啟ZK服務: bin/zkServer.sh restart
  • 連接服務器 : bin/zkCli.sh -server 127.0.0.1:2181

4.1.5 啟動並測試服務:

# 啟動服務
./bin/zkServer.sh start

# 啟動客戶端測試
./bin/zkCli.sh

4.2 單機偽集群

使用一台服務器,創建多個服務實現zookeeper偽集群。

4.2.1 創建集群目錄結構

這里創建clester目錄,為每個服務指定目錄。
data是數據目錄,conf是配置文件目錄,logs是日志目錄。

# 創建每個服務對應的目錄 這里使用2181,2182,2183三個端口 
mkdir -pv cluster/{2181/{conf,data,logs},2182/{conf,data,logs},2183/{conf,data,logs}}

4.2.2 指定服務機器名稱

zookeeper集群中的每台機器都知道其他的機器的信息,便於在集群出現問題后,重新選舉leader等。

# 指定服務機器名稱
echo 1 > cluster/2181/data/myid
echo 2 > cluster/2182/data/myid
echo 3 > cluster/2183/data/myid

4.2.3 創建服務配置文件

為每個服務配置指定的配置文件,主要修改端口號,數據、日志目錄。

# 進入2181服務目錄,修改其配置文件
vi cluster/2181/conf/zoo.cfg

# 更改端口號,數據、日志目錄,並配置集群機器信息
clientPort=2181
dataDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/data
dataLogDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/logs

server.1=127.0.0.1:3181:4181
server.2=127.0.0.1:3182:4182
server.3=127.0.0.1:3183:4183

這里是單機偽集群,通信端口和選舉端口都是不一樣的,正式使用不用機器,可以是不同ip和統一端口號。

# 復制配置文件到其他服務目錄
cp cluster/2181/conf/zoo.cfg cluster/2182/conf/zoo.cfg
cp cluster/2181/conf/zoo.cfg cluster/2183/conf/zoo.cfg

# 修改其他服務配置文件 
vi cluster/2182/conf/zoo.cfg
vi cluster/2182/conf/zoo.cfg

# vim 快速替換 %s/2181/2182/g

4.2.4 啟動集群服務

使用集群不同服務目錄配置文件,啟動對應服務。

./bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/conf/zoo.cfg
./bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2182/conf/zoo.cfg
./bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2183/conf/zoo.cfg

4.2.5 查看服務狀態

./bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/conf/zoo.cfg
./bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2182/conf/zoo.cfg
./bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2183/conf/zoo.cfg

通過status可以查看服務的狀態,我們啟動三個服務,有一個leader和兩個follower。
zookeeper服務狀態
可以看到2182是leader,2181和2183是follower。
我們嘗試把2182(leader)停止,再去查看狀態,可以看到重新選舉了2183為leader,2182重新啟動后會成為follower。
zookeeper服務狀態

5. zookeeper CLI

ZooKeeper命令行界面(CLI)用於與ZooKeeper集合進行交互以進行開發。它有助於調試和解決不同的選項。
要執行ZooKeeper CLI操作,首先打開ZooKeeper服務器(“bin/zkServer.sh start”),然后打開ZooKeeper客戶端(“bin/zkCli.sh”)。
命令幫助

5.1 節點屬性

屬性名 屬性說明
cZxid 數據節點創建的事務id
cZxid 數據節點創建的事務id。
ctime 數據節點創建的時間。
mZxid 數據節點最后一次更新時的事務id
mtime 數據節點最后一次更新的時間。
pZxid 數據節點的子節點最后一次被修改時的事務id。
cversion 子節點的更改次數。
dataVersion 節點數據的更改次數,也就是數據的版本。類似於關系型數據庫的樂觀鎖。
aclVersion 節點ACL權限的更改次數。
ephemeralOwner 如果節點是臨時節點,則該屬性表示創建該節點的會話SessionId。如果該節點是持久節點,該屬性為0,可以使用該屬性來判斷節點是否為臨時節點。
dataLength 數據的內容長度,單位字節。
numChildren 子節點的個數。

zxid:致使ZooKeeper節點狀態改變的每一個操作都將使節點接收到一個遞增的事務id號,並且這個時間戳全局有序。也就是說,也就是說,每個對節點的改變都將產生一個唯一的Zxid。如果Zxid1的值小於Zxid2的值,那么Zxid1所對應的事件發生在Zxid2所對應的事件之前。實際上,ZooKeeper的每個節點維護者三個Zxid值,為別為:cZxid、mZxid、pZxid。

5.2 常用操作

介紹關於節點的常用操作,包括節點的增刪改查,以及配額、同步等。

# 創建一個節點 -e為臨時節點 -s為順序節點 -c為容器節點 -t為ttl節點
create /file "file data"

# 獲取當前節點數據
get /file

# 設置節點數據
set /file "data"

# 創建子節點
create /file/test "data"

# 獲取當前節點狀態
stat /file

# 刪除一個節點
delete /file
# 刪除指定版本的節點
delete -v 1 /file

# 刪除節點以及其包含的子節點
deleteall /file

# 設置節點配置
# -n 子節點個數 -b 節點數據長度
# 超出配額並不會報錯,而是會在日志(*.out)中出現警告
setquota /data -n 3
setquota /data -b 100

# 查看配額
listquota /data

# 刪除配額
delquota /data

# 列出節點下面的所有子節點
ls -R /

# 強制同步所有的更新操作
sync /data

其他命令:

close:關閉當前連接
history:查看歷史執行指令

redo命令
再次執行某命令。
如redo 10
其中10為命令ID,需與history配合使用。

sync
由於請求在半數以上的zk server上生效就表示此請求生效,那么就會有一些zk server上的數據是舊的。sync命令就是強制同步所有的更新操作。

printwatches
在獲取節點數據、子節點列表等操作時,都可以添加watch參數監聽節點的變化,從而節點數據更改、子節點列表變更時收到通知,並輸出到控制台。默認是打開,可以設置參數將其關閉。
printwatches

6. 運維四字命令

zookeeper可以通過它自身提供的簡寫命令來和服務器進行交互,需要使用到nc命令.

# 安裝命令
yum install nc

在配置文件中開啟四字命令

4lw.commands.whitelist=*

查看配置信息:
nc 配置信息

conf
conf命令用於輸出 ZooKeeper服務器運行時使用的基本配置信息,包括clientPort、dataDir和tickTime等,以便運維人員能快速查看 ZooKeeper當前運行時的一些參數,如上圖所示。注意,conf命令輸出的配置信息僅僅是輸出一些最基本的配置參數。
另外,conf命令會根據當前的運行模式來決定輸出的信息。上圖所示的輸出信息是針對集群模式下的樣例,如果是單機模式(standalone),就不會輸出諸如initLimit、syncLimit、electionAlg和electionPort等集群相關的配置信息。

cons
cons命令用於輸出當前這台服務器上所有客戶端連接的詳細信息,包括每個客戶端的客戶端IP、會話ID和最后一次與服務器交互的操作類型等。

crst
crst命令是一個功能性命令,用於重置所有的客戶端連接統計消息

dump
dump命令用於輸出當前集群的所有會話信息,包括這些會話的會話ID,以及每個會話創建的臨時節點等信息。如果在Leader服務器上執行該命令的話,我們還能夠看到每個會話的超時時間。

envi
envi命令用於輸出 ZooKeeper所在服務器運行時的環境信息,包括os.version、java.version和user.home等。

ruok
ruok命令用於輸出當前 ZooKeeper服務器是否正在運行。該命令的名字非常有趣,其協議正好是“Are you ok”。執行該命令后,如果當前 ZooKeeper服務器正在運行,那么返回“imok”,否則沒有任何響應輸出。
請注意,ruok命令的輸出僅僅只能表明當前服務器是否正在運行,准確的講,只能說明2181端口打開着,同時四字命令執行流程正常,但是不能代表 ZooKeeper服務器是否運行正常。在很多時候,如果當前服務器無法正常處理客戶端的讀寫請求,甚至已經無法和集群中的其他機器進行通信,ruok命令依然返回“imok”。因此,一般來說,該命令並不是一個特別有用的命令,他不能反映 ZooKeeper服務器的工作狀態,想要更可靠的獲取更多 ZooKeeper運行狀態信息,可以使用下面馬上要講到的stat命令。

stat
stat命令用於獲取 ZooKeeper服務器的運行時狀態信息,包括基本的 ZooKeeper版本、打包信息、運行時角色、集群數據節點個數等消息,另外還會將當前服務器的客戶端連接信息打印出來。
除了一些基本的狀態信息外,stat命令還會輸出一些服務器的統計信息,包括延遲情況、收到請求數和返回的響應數等。注意,所有這些統計數據都可以通過srst命令進行重置。

srvr
srvr命令和stat命令的功能一致,唯一的區別是srvr不會將客戶端的連接情況輸出,僅僅輸出服務器的自身信息

srst
stst命令是一個功能行命令,用於重置所有服務器的統計信息。

wchs
wchs命令用於輸出當前服務器上管理的Watcher的概要信息。

wchc
wchc命令用於輸出當前服務器上管理的Watcher的詳細信息,以會話為單位進行歸組,同時列出被該會話注冊了Watcher的節點路徑。

wchp**
wchp命令和wchc命令非常類似,也是用於輸出當前服務器上管理的Watcher的詳細信息,不同點在於wchp命令的輸出信息以節點路徑為單位進行歸組。

mntr
mntr命令用於輸出比stat命令更為詳盡的服務器統計信息,包括請求處理的延遲情況,服務器內存數據庫大小和集群的數據同步情況。在輸出結果中,每一行都是一個key-value的鍵值對,運維人員可以,根據這些輸出信息進行 ZooKeeper的運行時狀態監控。

如果在Leader服務器上執行該命令的話,可以獲取比Follower服務器更多的信息。

7. ACL權限

ACL全稱為Access Control List(訪問控制列表),用於控制資源的訪問權限。ZooKeeper使用ACL來控制對其znode(ZooKeeper數據樹的數據節點)的訪問。ACL實現與UNIX文件訪問權限非常相似:它使用權限位來允許/禁止針對節點的各種操作以及位應用的范圍。與標准UNIX權限不同,ZooKeeper節點不受用戶(文件所有者),組和world(其他)的三個標准范圍的限制。

zk利用ACL策略控制節點的訪問權限,如節點數據讀寫、節點創建、節點刪除、讀取子節點列表、設置節點權限等。

在傳統的文件系統中,一個文件擁有某個組的權限即擁有了組里的所有權限,文件或子目錄默認會繼承自父目錄的ACL。而在Zookeeper中,znode的ACL是沒有繼承關系的,每個znode的權限都是獨立控制的,只有客戶端滿足znode設置的權限要求時,才能完成相應的操作。

Zookeeper的ACL,分為三個維度:scheme、id、permission,通常表示為:scheme🆔permission,schema代表授權策略,id代表用戶,permission代表權限。下面分別講述一下這三個屬性:

7.1 scheme

scheme即采取的授權策略,每種授權策略對應不同的權限校驗方式。

  • world:默認方式,相當於全世界都能訪問
  • auth:不使用任何id,表示任何經過身份驗證的用戶。
  • digest:即用戶名:密碼這種方式認證,這也是業務系統中最常用的,使用username:password字符串生成MD5哈希,然后將其用作ACL的ID標識。通過以明文形式發送 例如:wangsaichao:123456 來完成身份驗證。在ACL中使用時,表達式將是wangsaichao:G2RdrM8e0u0f1vNCj/TI99ebRMw=。
  • ip:使用Ip地址認證

7.2 id

id是驗證模式,不同的scheme,id的值也不一樣。scheme為digest時,id的值為:username:BASE64(SHA1(password)),scheme為ip時,id的值為客戶端的ip地址。scheme為world時,id的值為anyone。scheme為auth時,id為 username:password。

7.3 permission

CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、刪、改、查、管理權限,這5種權限簡寫為crwda。
這5種權限中,delete是指對子節點的刪除權限,其它4種權限指對自身節點的操作權限。

  • CREATE(c):創建子節點的權限
  • DELETE(d):刪除節點的權限
  • READ(r):讀取節點數據的權限
  • WRITE(w):修改節點數據的權限
  • ADMIN(a):設置子節點權限的權限
    注意:
    要修改某個節點的ACL屬性,必須具有read、admin二種權限。
    要刪除某個節點下的子節點,必須具有對父節點的read權限,以及父節點的delete權限。

7.4 相關命令

命令 語法 介紹
getAcl getAcl 讀取ACL權限
setAcl setAcl 設置ACL權限
addauth addauth 添加認證用戶

7.6 使用介紹

7.6.1 super

一旦我們為某一個節點設置了acl,那么其余的未授權的節點是無法訪問或者操作該節點的,那么系統用久了以后,假如忘記了某一個節點的密碼,那么就無法再操作這個節點了,所以需要這個super超級管理員用戶權限,其作用還是很大的。

在啟動服務的時候,添加管理員賬號

# 獲取加密后的賬號信息root:root
[root@VM-0-12-centos apache-zookeeper-3.6.2]# echo -n root:root | openssl dgst -binary -sha1 | openssl base64
qiTlqPLK7XM2ht3HMn02qRpkKIE=

# 修改bin下面的zkServer.sh
# 找到 nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \ 這一行,大概在158行
# 在后面添加一句
"-Dzookeeper.DigestAuthenticationProvider.superDigest=root:qiTlqPLK7XM2ht3HMn02qRpkKIE=" \

# 完成的命令如下
nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
"-Dzookeeper.DigestAuthenticationProvider.superDigest=root:qiTlqPLK7XM2ht3HMn02qRpkKIE=" \
"-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \

# 重新啟動服務,登錄
addauth digest root:root

7.6.2 word

語法:world:anyone:cdrwa
創建節點默認的scheme,所有人都可以訪問。

# 創建一個節點
[zk: 127.0.0.1:2181(CONNECTED) 10] create /node01 "data"
Created /node01

# 查看ACL
[zk: 127.0.0.1:2181(CONNECTED) 11] getAcl /node01
'world,'anyone
: cdrwa

# 設置所有人可以讀
setAcl /node01 world:anyone:r

# 此時只有r權限,寫操作會失敗
[zk: localhost:2181(CONNECTED) 17] set /test a
Authentication is not valid : /test

# 添加認證用戶-管理員 即可完成set
addauth digest root:root

7.6.3 digest

語法:digest:username:BASE64(SHA1(password)):cdrwa

digest:是授權方式
username:BASE64(SHA1(password)):是id部分
cdrwa:權限部份

用戶名+密碼授權訪問方式,也是常用的一種授權策略。
id部份是用戶名和密碼做sha1加密再做BASE64加密后的組合,比如設置一個節點的用戶名為user,密碼為123456,則表示方式為:
原:user:BASE64(SHA1(user:123456))
正確:user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=。

使用lunix加密

[root@VM-0-12-centos ~]# echo -n user:123456 | openssl dgst -binary -sha1 | openssl base64
6DY5WhzOfGsWQ1XFuIyzxkpwdPo=

使用zookeeper工具類加密

[root@VM-0-12-centos ~]# java -cp /usr/local/zookeeper/apache-zookeeper-3.6.2/lib/zookeeper-3.6.2.jar:/usr/local/zookeeper/apache-zookeeper-3.6.2/lib/slf4j-api-1.7.25.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider user:123456
user:123456->user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=

使用java代碼進行加密

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
</dependency>
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.junit.Test;
import java.security.MessageDigest;
import org.apache.commons.codec.binary.Base64;

public class test {

    /**
     * 使用第三方包生成
     * @throws Exception
     */
    @Test
    public void test1() throws Exception{

        String usernameAndPassword = "user:123456";
        byte digest[] = MessageDigest.getInstance("SHA1").digest(usernameAndPassword.getBytes());
        Base64 base64 = new Base64();
        String encodeToString = base64.encodeToString(digest);
        System.out.println(encodeToString);

    }

    /**
     * 使用zookeeper提供的方法生成
     * @throws Exception
     */
    @Test
    public void test2() throws Exception{
        String generateDigest = DigestAuthenticationProvider.generateDigest("user:123456");
        System.out.println(generateDigest);
    }
}

使用介紹

# 創建一個節點
[zk: 127.0.0.1:2181(CONNECTED) 14] create /file file-value
Created /file

# 設置ACL
[zk: 127.0.0.1:2181(CONNECTED) 15] setAcl /file digest:user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=:crwda
[zk: 127.0.0.1:2181(CONNECTED) 16] getAcl /file
Authentication is not valid : /file

# 添加一個認證用戶
[zk: 127.0.0.1:2181(CONNECTED) 17] addauth digest user:123456
[zk: 127.0.0.1:2181(CONNECTED) 18] getAcl /file
'digest,'user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=
: cdrwa

7.6.4 auth

digest使用時是密文密碼,可以直接進行setAcl
auth使用時是明文密碼,setAcl前需要先添加認證addauth

# 創建一個節點
[zk: localhost:2181(CONNECTED) 1] create /node
Created /node

# 添加一個認證用戶,setAcl前先執行這步
[zk: localhost:2181(CONNECTED) 2] addauth digest user:password

# setAcl
[zk: localhost:2181(CONNECTED) 3] setAcl /node auth:user:password:cdrwa

# getAcl
[zk: localhost:2181(CONNECTED) 4] getAcl /node
'digest,'user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=
: cdrwa

7.6.5 ip

基於客戶端IP地址校驗,限制只允許指定的客戶端能操作znode。
比如,設置某個節點只允許IP為127.0.0.1的客戶端能讀寫該寫節點的數據:ip:127.0.0.1:rw

# 創建一個節點
[zk: localhost:2181(CONNECTED) 6] create /newnode
Created /newnode

# setAcl
[zk: localhost:2181(CONNECTED) 7] setAcl /newnode ip:127.0.0.1:cdrwa

# getAcl 
[zk: localhost:2181(CONNECTED) 8] getAcl /newnode 
Authentication is not valid : /newnode

# 認證
[zk: localhost:2181(CONNECTED) 9] addauth digest root:root
[zk: localhost:2181(CONNECTED) 10] getAcl /newnode 
'ip,'127.0.0.1
: cdrwa

8. zookeeper api

java操作zookeeper
pom添加依賴

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
</dependency>

8.1 連接zookeeper

api 提供的構造方法
構造方法
注意:每個構造器創建連接都是異步的,構造方法啟動與服務器的連接,然后立馬返回,此時會話處於CONNECTING狀態,通過watcher通知。此通知可以在構造方法調用返回之前或之后的任何時候到來。會話創建成功之后,狀態會改為CONNECTED。

參數介紹:

參數名 描述
connectString 要創建ZooKeeper客戶端對象,應用程序需要傳遞一個連接字符串,其中包含逗號分隔的host:port列表,每個對應一個ZooKeeper服務器。例如:127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183實例化的ZooKeeper客戶端對象將從connectString中選擇一個任意服務器並嘗試連接到它。如果建立連接失敗,將嘗試連接字符串中的另一個服務器(順序是非確定性的,因為是隨機),直到建立連接。客戶端將繼續嘗試,直到會話顯式關閉。在3.2.0版本之后,也可以在connectString后面添加后綴字符串,如:127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/app/a,客戶端連接上ZooKeeper服務器之后,所有對ZooKeeper的操作,都會基於這個根目錄。例如,客戶端對/foo/bar的操作,都會指向節點/app/a/foo/bar——這個目錄也叫Chroot,即客戶端隔離命名空間。
sessionTimeout 會話超時(以毫秒為單位)客戶端和服務端連接創建成功之后,ZooKeeper中會建立一個會話,在一個會話周期內,ZooKeeper客戶端和服務端之間會通過心跳檢測機制來維持會話的有效性,一旦在sessionTimeout時間內沒有進行有效的心跳檢測,會話就會失效。
watcher 創建ZooKeeper客戶端對象時,ZooKeeper允許客戶端在構造方法中傳入一個接口Watcher(org.apache.zookeeper.Watcher)的實現類對象來作為默認的Watcher事件通知處理器。當然,該參數可以設置為null以表明不需要設置默認的Watcher處理器。如果設置為null,日志中會有空指針異常,但是並不影響使用。
canBeReadOnly 3.4之后添加的boolean類型的參數,用於標識當前會話是否支持“read-only”模式。默認情況下,在ZooKeeper集群中,一個機器如果和集群中過半以上機器失去了網絡連接,那么這個機器將不再處理客戶端請求(包括讀寫請求)。但是在某些使用場景下,當ZooKeeper服務器發生此類故障的時候,我們還是希望ZooKeeper服務器能夠提供讀服務(當然寫服務肯定無法提供)——這就是ZooKeeper的“read-only”模式。
sessionId 和 sessionPasswd 會話id和 會話密碼,這兩個參數能夠唯一確定一個會話,同時客戶端使用這兩個參數實現客戶端會話復用,從而達到恢復會話的效果,使用方法:第一次連接上ZooKeeper服務器后,客戶端使用getSessionId()和getSessionPasswd()獲取這兩個值,如果需要會話復用,在重新創建ZooKeeper客戶端對象的時候可以傳過去,如果不需要會話復用,請使用不需要這些參數的其他構造函數。
HostProvider 客戶端地址列表管理器

實例參考:

package cn.sivan.test;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

/**
 * zookeeper的連接和數據庫的連接不同,數據庫通過DriverManager的getConnect方法就可以直接獲取到連接。
 * 但zookeeper在獲取連接的過程中,使用了Future。也就意味着,new之后所拿到的僅僅是一個zookeeper對象,而這個對象可能還沒有連接到zookeeper服務。
 */
public class Connect implements Watcher {

    private CountDownLatch countDownLatch = new CountDownLatch(1);

    @Override
    public void process(WatchedEvent watchedEvent) {

        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            //連接創建成功,喚醒等待線程。
            countDownLatch.countDown();
        }
    }

    /**
     * 連接zookeeper
     * @param connStr address
     * @param timeout 超時時間
     */
    public ZooKeeper connect(String connStr, int timeout) {

        try {
            //創建zookeeper
            ZooKeeper zooKeeper = new ZooKeeper(connStr, timeout, this);

            //當前線程 等待連接連接成功的通知
            countDownLatch.await();
            return zooKeeper;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

EventType:Watch事件介紹

  • None
  • NodeCreated
  • NodeDeleted
  • NodeDataChanged
  • NodeChildrenChanged
  • DataWatchRemoved
  • ChildWatchRemoved
  • PersistentWatchRemoved

ZookeeperState:

state 說明
Disconnected 客戶端處於斷開狀態 - 未連接
SyncConnected 客戶端處於連接狀態 - 已連接
AuthFailed 驗證失敗狀態
ConnectedReadOnly 客戶端連接到只讀服務器
SaslAuthenticated 客戶端已通過 SASL 認證,可以使用其 SASL 授權的權限執行 Zookeeper 操作
Expired 會話已失效
Closed 客戶端已關閉,客戶端調用時在本地生成

8.2 API使用

package cn.sivan.test;

import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ZookeeperSimple {

    public static void main(String[] args) throws Exception {
        String path = "/chodble";
        String address = "127.0.0.1:2181";
        int timeout = 3000;

        Connect zkConnect = new Connect();
        ZooKeeper zk = zkConnect.connect(address, timeout);

        //判斷節點是否存在
        if (null == zk.exists(path, true)) {

            //設置認證方式
            Id ADMIN_IDS = new Id("digest", DigestAuthenticationProvider.generateDigest("root:root"));
            Id USER_IDS = new Id("digest", DigestAuthenticationProvider.generateDigest("user:user"));
            Id ANYONE_ID_UNSAFE = ZooDefs.Ids.ANYONE_ID_UNSAFE;

            /**
             *設置ACL權限
             *Create  允許對子節點Create 操作
             *Read    允許對本節點GetChildren 和GetData 操作
             *Write   允許對本節點SetData 操作
             *Delete  允許對子節點Delete 操作(本節點也可以刪除)
             *Admin   允許對本節點setAcl 操作
             *ALL = READ | WRITE | CREATE | DELETE | ADMIN;
             */
            List<ACL> aclList = new ArrayList<>();
            aclList.add(new ACL(ZooDefs.Perms.ADMIN, ADMIN_IDS));
            //aclList.add(new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.READ | ZooDefs.Perms.WRITE | ZooDefs.Perms.DELETE, USER_IDS));
            aclList.add(new ACL(ZooDefs.Perms.READ, ANYONE_ID_UNSAFE));

            /**
             * 創建根節點
             * 不支持遞歸創建,即無法在父節點不存在的情況下創建一個子節點
             * 一個節點已經存在了,那么創建同名節點的時候,會拋出NodeExistException異常。如果是順序節點,那么永遠不會拋出NodeExistException異常
             * 臨時節點不能有子節點
             *
             * CreateMode
             * PERSISTENT : 持久節點
             * PERSISTENT_SEQUENTIAL : 持久順序節點
             * EPHEMERAL : 臨時節點
             * EPHEMERAL_SEQUENTIAL : 臨時順序節點
             * CONTAINER : 容器節點
             * PERSISTENT_WITH_TTL : 持久TTL節點
             * PERSISTENT_SEQUENTIAL_WITH_TTL : 持久順序TTL節點
             */
            zk.create(path, "寧川".getBytes(), aclList, CreateMode.PERSISTENT);

            //設置Acl
            zk.setACL(path, Collections.singletonList(new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.READ | ZooDefs.Perms.WRITE | ZooDefs.Perms.DELETE, USER_IDS)), -1);
        }

        //進行認證
        zk.addAuthInfo("digest", "user:user".getBytes());

        //查看ACL權限
        Stat stat = null;
        if ((stat = zk.exists(path, true)) != null) {
            System.out.println("查看節點權限:" + zk.getACL(path, stat));
        }

        //獲取子節點數量
        System.out.println("獲取子節點數量:" + zk.getAllChildrenNumber(path));

        //獲取所有的節點
        System.out.println("所有的節點:" + zk.getChildren("/", false));

        //獲取節點的值,並設置監聽
        System.out.println("獲取節點的值:" + new String(zk.getData(path, (event -> {
            System.out.println("getWatch:" + event.toString());
        }), stat)));

        //添加監聽
        zk.addWatch(path, (event) -> {
            System.out.println("addWatch:" + event.toString());
        }, AddWatchMode.PERSISTENT);

        //設置節點數據
        // 1 -自動維護
        System.out.println("設置節點數據:" + zk.setData(path, "123".getBytes(), -1));

        /**
         * 異步創建一個 臨時順序節點,ACL為 ip:127.0.0.1:c
         */
        zk.create("/node",
                "123".getBytes(),
                Collections.singletonList(new ACL(ZooDefs.Perms.CREATE, new Id("ip", "127.0.0.1"))),
                CreateMode.EPHEMERAL_SEQUENTIAL,
                //new AsyncCallback.StringCallback()
                (rc, path1, ctx, name) -> {
                    System.out.println("rc:" + rc);
                    System.out.println("path:" + path1);
                    System.out.println("ctx:" + ctx);
                    System.out.println("name:" + name);
                }, "傳給服務端的內容,會在異步回調時傳回來");
        //等待執行結果
        Thread.sleep(2000);

        //刪除節點
        if ((stat = zk.exists(path, true)) != null) {
            List<String> subPaths = zk.getChildren(path, false);
            if (subPaths.isEmpty()) {
                zk.delete(path, stat.getVersion());
            } else {
                for (String subPath : subPaths) {
                    zk.delete(path + "/" + subPath, -1);
                }
            }
        }
    }
}

ZooDefs.Ids

public interface Ids {

        /**
         * world:anyone:adrwa
         * This Id represents anyone.
         */
        Id ANYONE_ID_UNSAFE = new Id("world", "anyone");

        /**
         * 認證后可操作
         * This Id is only usable to set ACLs. It will get substituted with the
         * Id's the client authenticated with.
         */
        Id AUTH_IDS = new Id("auth", "");

        /**
         * 完全開放的ACL,任何連接的客戶端都可以操作該屬性znode
         * This is a completely open ACL .
         */
        @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION", justification = "Cannot break API")
        ArrayList<ACL> OPEN_ACL_UNSAFE = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.ALL, ANYONE_ID_UNSAFE)));

        /**
         * 只有創建者才有ACL權限
         * This ACL gives the creators authentication id's all permissions.
         */
        @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION", justification = "Cannot break API")
        ArrayList<ACL> CREATOR_ALL_ACL = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.ALL, AUTH_IDS)));

        /**
         * 只讀ACL
         * This ACL gives the world the ability to read.
         */
        @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION", justification = "Cannot break API")
        ArrayList<ACL> READ_ACL_UNSAFE = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.READ, ANYONE_ID_UNSAFE)));
}

CreateModel

public enum CreateMode {

    /**
     * 持久節點
     * The znode will not be automatically deleted upon client's disconnect.
     */
    PERSISTENT(0, false, false, false, false),

    /**
     * 持久順序節點
     * The znode will not be automatically deleted upon client's disconnect,
     * and its name will be appended with a monotonically increasing number.
     */
    PERSISTENT_SEQUENTIAL(2, false, true, false, false),

    /**
     * 臨時節點
     * The znode will be deleted upon the client's disconnect.
     */
    EPHEMERAL(1, true, false, false, false),

    /**
     * 臨時順序節點
     * The znode will be deleted upon the client's disconnect, and its name
     * will be appended with a monotonically increasing number.
     */
    EPHEMERAL_SEQUENTIAL(3, true, true, false, false),

    /**
     * 容器節點
     * The znode will be a container node. Container
     * nodes are special purpose nodes useful for recipes such as leader, lock,
     * etc. When the last child of a container is deleted, the container becomes
     * a candidate to be deleted by the server at some point in the future.
     * Given this property, you should be prepared to get
     * {@link org.apache.zookeeper.KeeperException.NoNodeException}
     * when creating children inside of this container node.
     */
    CONTAINER(4, false, false, true, false),

    /**
     * 持久TTL節點
     * The znode will not be automatically deleted upon client's disconnect.
     * However if the znode has not been modified within the given TTL, it
     * will be deleted once it has no children.
     */
    PERSISTENT_WITH_TTL(5, false, false, false, true),

    /**
     * 持久順序TTL節點
     * The znode will not be automatically deleted upon client's disconnect,
     * and its name will be appended with a monotonically increasing number.
     * However if the znode has not been modified within the given TTL, it
     * will be deleted once it has no children.
     */
    PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
}

9. Watch

一個Watch事件是一個一次性的觸發器,當被設置了Watch的數據發生了改變的時候,則服務器將這個改變發送給設置了Watch的客戶端,以便通知它們。

watch機制的特點:

  • 一次性觸發 數據發生改變時,一個watcher event會被發送到client,但是client只會收到一次這樣的信息。
  • watcher event異步發送 watcher 的通知事件從server發送到client是異步的,這就存在一個問題,不同的客戶端和服務器之間通過socket進行通信,由於網絡延遲或其他因素導致客戶端在不通的時刻監聽到事件,由於Zookeeper本身提供了ordering guarantee,即客戶端監聽事件后,才會感知它所監視znode發生了變化。
  • 數據監視 Zookeeper有數據監視和子數據監視 getdata() and exists() 設置數據監視,getchildren()設置了子節點監視
  • 注冊watcher getData、exists、getChildren
  • 觸發watcher create、delete、setData

常見監聽事件:

nodedatachanged # 節點數據改變
nodecreate # 節點創建事件
nodedelete #節點刪除事件
nodechildrenchanged # 子節點改變事件
package cn.sivan.test;

import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

import java.util.Collections;

public class WatchTest {
    public static void main(String[] args) throws Exception {

        String path = "/chodble2";
        String pathValue = "root-value";

        String subPath = path + "/node";
        String subPathValue = "sbu-value";

        String address = "127.0.0.1:2181";
        int timeout = 3000;

        Connect zkConnect = new Connect();
        ZooKeeper zk = zkConnect.connect(address, timeout);

        //判斷節點是否存在
        Stat stat = null;
        if ((stat = zk.exists(path, true)) == null) {

            //創建節點
            zk.create(path, pathValue.getBytes(), Collections.singletonList(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE)), CreateMode.PERSISTENT);
        }

        //進行認證
        zk.addAuthInfo("digest", "user:user".getBytes());

        //設置節點監聽
        zk.addWatch(path, event -> {
            System.out.println("addWatch-1:" + event);
        }, AddWatchMode.PERSISTENT);

        //獲取值
        zk.getData(path, true, new Stat());

        //設置值
        zk.setData(path, "new-data".getBytes(), -1);

        //測試通知
        for (int i = 0; i < 10; i++) {
            //重新設置值
            zk.setData(path, ("new-data" + i).getBytes(), -1);
        }

        //設置權限
        zk.setACL(path, Collections.singletonList(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("user:user")))), -1);

        //創建子節點
        zk.create(subPath, subPathValue.getBytes(), Collections.singletonList(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.AUTH_IDS)), CreateMode.PERSISTENT);

        //獲取子節點的值
        zk.getData(subPath, true, new Stat());

        //設置子節點的值
        zk.setData(subPath, "sub-new-data".getBytes(), -1);

        //設置子節點權限
        zk.setACL(subPath, Collections.singletonList(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("user:user")))), -1);

        //獲取子節點的個數
        zk.getAllChildrenNumber(path);

        //列出所有的子節點
        zk.getChildren(path, true);

        //查看節點ACl
        if ((stat = zk.exists(path, true)) != null) {
            System.out.println("查看節點權限:" + zk.getACL(path, stat));
        }

        //刪除子節點
        zk.delete(subPath, -1);

        //刪除節點
        zk.delete(path, -1);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM