萬字長文帶你入門Zookeeper!!!



點擊上方碼農沉思錄,選擇「設爲星標」javascript

優質文章,及時送達css

前言 java

Zookeeper 相信你們都據說過,最典型的使用就是做爲服務註冊中心。今天陳某帶你們從零基礎入門 Zookeeper,看了本文,你將會對 Zookeeper 有了初步的瞭解和認識。node

注意:本文基於 Zookeeper 的版本是 3.4.14,最新版本的在使用上會有一些出入,可是企業如今使用的大部分都是 3.4x 版本的。nginx

Zookeeper 概述web

Zookeeper 是一個分佈式協調服務的開源框架。主要用來解決分佈式集羣中應用系統的一致性問題,例如怎樣避免同時操做同一數據形成髒讀的問題。sql

ZooKeeper 本質上是一個分佈式的小文件存儲系統。提供基於相似於文件系 統的目錄樹方式的數據存儲,而且能夠對樹中的節點進行有效管理。從而用來維護和監控你存儲的數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達 到基於數據的集羣管理。諸如:統一命名服務、分佈式配置管理、分佈式消息隊列、分佈式鎖、分佈式協調等功能。shell

Zookeeper 特性apache

  1. 全局數據一致:每一個 server 保存一份相同的數據副本,client 不管連 接到哪一個 server,展現的數據都是一致的,這是最重要的特徵。api

  2. 可靠性:若是消息被其中一臺服務器接受,那麼將被全部的服務器接受。

  3. 順序性:包括全局有序和偏序兩種:全局有序是指若是在一臺服務器上 消息 a 在消息 b 前發佈,則在全部 Server 上消息 a 都將在消息 b 前被 發佈;偏序是指若是一個消息 b 在消息 a 後被同一個發送者發佈,a 必將排在 b 前面。

  4. 數據更新原子性:一次數據更新要麼成功(半數以上節點成功),要麼失 敗,不存在中間狀態。

  5. 實時性:Zookeeper 保證客戶端將在一個時間間隔範圍內得到服務器的更新信息,或者服務器失效的信息。

Zookeeper 節點類型

Znode 有兩種,分別爲臨時節點和永久節點。

臨時節點:該節點的生命週期依賴於建立它們的會話。一旦會話結束,臨時節點將被自動刪除,固然能夠也能夠手動刪除。臨時節點不容許擁有子節點。

永久節點:該節點的生命週期不依賴於會話,而且只有在客戶端顯示執行刪除操做的時候,他們才能被刪除。

節點的類型在建立時即被肯定,而且不能改變。

Znode 還有一個序列化的特性,若是建立的時候指定的話,該 Znode 的名字後面會自動追加一個不斷增長的序列號。序列號對於此節點的父節點來講是惟一的,這樣便會記錄每一個子節點建立的前後順序。它的格式爲"%10d"(10 位數字,沒有數值的數位用 0 補充,例如「0000000001」)。

這樣便會存在四種類型的 Znode 節點,分類以下:

PERSISTENT:永久節點

EPHEMERAL:臨時節點

PERSISTENT_SEQUENTIAL:永久節點、序列化

EPHEMERAL_SEQUENTIAL:臨時節點、序列化

ZooKeeper Watcher

ZooKeeper 提供了分佈式數據發佈/訂閱功能,一個典型的發佈/訂閱模型系統定義了一種一對多的訂閱關係,能讓多個訂閱者同時監聽某一個主題對象,當這個主題對象自身狀態變化時,會通知全部訂閱者,使他們可以作出相應的處理。

觸發事件種類不少,如:節點建立,節點刪除,節點改變,子節點改變等。

總的來講能夠歸納 Watcher 爲如下三個過程:客戶端向服務端註冊 Watcher、服務端事件發生觸發 Watcher、客戶端回調 Watcher 獲得觸發事件狀況。

Watcher 機制特色

一次性觸發 :事件發生觸發監聽,一個 watcher event 就會被髮送到設置監聽的客戶端,這種效果是一次性的,後續再次發生一樣的事件,不會再次觸發。

事件封裝 :ZooKeeper 使用 WatchedEvent 對象來封裝服務端事件並傳遞。WatchedEvent 包含了每個事件的三個基本屬性:通知狀態(keeperState),事件類型(EventType)和節點路徑(path)。

event 異步發送 :watcher 的通知事件從服務端發送到客戶端是異步的。

先註冊再觸發 :Zookeeper 中的 watch 機制,必須客戶端先去服務端註冊監聽,這樣事件發送纔會觸發監聽,通知給客戶端。

經常使用 Shell 命令 新增節點create [-s] [-e] path data

-s:表示建立有序節點

-e:表示建立臨時節點

建立持久化節點:

create /test 1234

## 子節點create /test/node1 node1

建立持久化有序節點:

完整的節點名稱是a0000000001create /a a
Created /a0000000001
## 完整的節點名稱是b0000000002create /b b
Created /b0000000002


建立臨時節點:

create -e /a a

建立臨時有序節點:

完整的節點名稱是a0000000001create -e -s /a a
Created /a0000000001
更新節點set [path] [data] [version]


path:節點路徑

data:數據

version:版本號

修改節點數據:

set /test aaa

## 修改子節點set /test/node1 bbb

基於數據版本號修改,若是修改的節點的版本號(dataVersion)不正確,拒絕修改

set /test aaa 1

刪除節點delete [path] [version]

path:節點路徑

version:版本號,版本號不正確拒絕刪除

刪除節點

delete /test

## 版本號刪除delete /test 2

遞歸刪除,刪除某個節點及後代

rmr /test

查看節點數據和狀態

命令格式以下:

get path

獲取節點詳情:

獲取節點詳情get /node1
## 節點內容aaa
cZxid = 0x6
ctime = Sun Apr 05 14:50:10 CST 2020
mZxid = 0x6
mtime = Sun Apr 05 14:50:10 CST 2020
pZxid = 0x7
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 1


節點各個屬性對應的含義以下:

cZxid:數據節點建立時的事務 ID。

ctime:數據節點建立時間。

mZxid:數據節點最後一次更新時的事務 ID。

mtime:數據節點最後一次更新的時間。

pZxid:數據節點的子節點最後一次被修改時的事務 ID。

cversion:子節點的更改次數。

dataVersion:節點數據的更改次數。

aclVersion :節點 ACL 的更改次數。

ephemeralOwner:若是節點是臨時節點,則表示建立該節點的會話的 SessionID。若是節點是持久化節點,值爲 0。

dataLength :節點數據內容的長度。

numChildren:數據節點當前的子節點的個數。

查看節點狀態stat path

stat命令和get命令類似,不過這個命令不會返回節點的數據,只返回節點的狀態屬性。

stat /node1
## 節點狀態信息,沒有節點數據cZxid = 0x6ctime = Sun Apr 05 14:50:10 CST 2020mZxid = 0x6mtime = Sun Apr 05 14:50:10 CST 2020pZxid = 0x7cversion = 1dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 3numChildren = 1


查看節點列表

查看節點列表有ls path和ls2 path兩個命令。後者是前者的加強,不只會返回節點列表還會返回當前節點的狀態信息。

ls path:ls /
## 僅僅返回節點列表[zookeeper, node1]
ls2 path:ls2 /
## 返回節點列表和當前節點的狀態信息[zookeeper, node1]cZxid = 0x0ctime = Thu Jan 01 08:00:00 CST 1970mZxid = 0x0mtime = Thu Jan 01 08:00:00 CST 1970pZxid = 0x6cversion = 2dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 0numChildren = 2

監聽器 get path watch

使用get path watch註冊的監聽器在節點內容發生改變時,向客戶端發送通知,注意 Zookeeper 的觸發器是一次性的,觸發一次後會當即生效。

get /node1 watch
## 改變節點數據set /node1 bbb
## 監聽到節點內容改變了WATCHER::WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1

監聽器 stat path watch

stat path watch註冊的監聽器可以在節點狀態發生改變時向客戶端發出通知。好比節點數據改變、節點被刪除等。

stat /node2 watch

## 刪除節點node2delete /node2

## 監聽器監聽到了節點刪除WATCHER::

WatchedEvent state:SyncConnected type:NodeDeleted path:/node2

監聽器 ls/ls2 path watch

使用ls path watch或者ls2 path watch註冊的監聽器,可以監聽到該節點下的子節點的增長和刪除操做。

ls /node1 watch

## 建立子節點create /node1/b b

## 監聽到了子節點的新增WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/node1

Zookeeper 的 ACL 權限控制

zookeeper 相似文件控制系統,client 能夠建立,刪除,修改,查看節點,那麼如何作到權限控制的呢?zookeeper 的access control list 訪問控制列表能夠作到這一點。

ACL 權限控制,使用scheme:id:permission來標識。

權限模式(scheme):受權的策略

受權對象(id):受權的對象

權限(permission):授予的權限

權限控制是基於每一個節點的,須要對每一個節點設置權限。

每一個節點支持設置多種權限控制方案和多個權限。

子節點不會繼承父節點的權限,客戶端無權訪問某節點,但可能能夠訪問它的子節點。

例如:根據 IP 地址進行受權,命令以下:

setACl /node1 ip:192.168.10.1:crdwa
權限模式

權限模式便是採用何種方式受權。

world:只有一個用戶,anyone,表示登陸 zookeeper 全部人(默認的模式)。

ip:對客戶端使用 IP 地址認證。

auth:使用已添加認證的用戶認證。

digest:使用用戶名:密碼方式認證。

受權對象

給誰受權,受權對象的 ID 指的是權限賦予的實體,例如 IP 地址或用戶。

授予的權限

授予的權限包括create、delete、read、writer、admin。也就是增、刪、改、查、管理的權限,簡寫cdrwa。

注意:以上 5 種權限中,delete是指對子節點的刪除權限,其餘 4 種權限是對自身節點的操做權限。

create:簡寫c,能夠建立子節點。

delete:簡寫d,能夠刪除子節點(僅下一級節點)。

read:簡寫r,能夠讀取節點數據以及顯示子節點列表。

write:簡寫w,能夠更改節點數據。

admin:簡寫a,能夠設置節點訪問控制列表權限。

受權相關命令

getAcl [path]:讀取指定節點的 ACL 權限。

setAcl [path] [acl]:設置 ACL

addauth :添加認證用戶,和 auth,digest 受權模式相關。

world 受權模式案例

zookeeper 中默認的受權模式,針對登陸 zookeeper 的任何用戶授予指定的權限。命令以下:

setAcl [path] world:anyone:[permission]

path:節點

permission:授予的權限,好比cdrwa

去掉不能讀取節點數據的權限:

獲取權限列表(默認的)getAcl /node2
'world,'anyone
: cdrwa
## 去掉讀取節點數據的的權限,去掉rsetAcl /node2 world:anyone:cdwa
## 再次獲取權限列表getAcl /node2
'world,'anyone
: cdwa
## 獲取節點數據,沒有權限,失敗get /node2
Authentication is not valid : /node2
IP 受權模式案例


針對登陸用戶的 ip 進行權限限制。命令以下:

setAcl [path] ip:[ip]:[acl]

遠程登陸 zookeeper 的命令以下:

./zkCli.sh -server ip

設置192.168.10.1這個 ip 的增刪改查管理的權限。

setAcl /node2 ip:192.168.10.1:crdwa

Auth 受權模式案例

auth 受權模式須要有一個認證用戶,添加命令以下:

addauth digest [username]:[password]

設置 auth 受權模式命令以下:

setAcl [path] auth:[user]:[acl]

爲chenmou這個帳戶添加 cdrwa 權限:

添加一個認證帳戶addauth digest chenmou:123456
## 添加權限setAcl /node2 auth:chenmou:crdwa
多種模式受權


zookeeper 中同一個節點可使用多種受權模式,多種受權模式用,分隔。

建立節點create /node3
## 添加認證用戶addauth chenmou:123456
## 添加多種受權模式setAcl /node3 ip:192.178.10.1:crdwa,auth:chenmou:crdwa
ACL 超級管理員


zookeeper 的權限管理模式有一種叫作super,該模式提供一個超管能夠方便的訪問任何權限的節點。

假設這個超管是super:admin,須要先爲超管生成密碼的密文:

echo -n super:admin | openssl dgst -binary -sha1 |openssl base64## 執行完生成了祕鑰xQJmxLMiHGwaqBvst5y6rkB6HQs=

打開zookeeper目錄下/bin/zkServer.sh,找到以下一行:

nohup JAVA"−Dzookeeper.log.dir=JAVA"−Dzookeeper.log.dir={ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"

在後面添加一行腳本,以下:

"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="

此時完整的腳本以下:

nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs=" \ -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &

重啓 zookeeper

重啓完成以後此時超管即配置完成,若是須要使用,則使用以下命令:

 addauth digest super:admin
Curator 客戶端

Curator 是 Netflix 公司開源的一個 Zookeeper 客戶端,與 Zookeeper 提供的原生客戶端相比,Curator 的抽象層次更高,簡化了 Zookeeper 客戶端的開發量。

添加依賴 

org.apache.curatorcurator-recipes            4.0.0org.apache.zookeeper           zookeeper        org.apache.zookeeperzookeeper 3.4.10

創建鏈接

客戶端創建與 Zookeeper 的鏈接,這裏僅僅演示單機版本的鏈接,以下:

//建立CuratorFramework,用來操做apiCuratorFramework client = CuratorFrameworkFactory.builder()//ip地址+端口號,若是是集羣,逗號分隔 .connectString("120.26.101.207:2181")//會話超時時間 .sessionTimeoutMs(5000)//超時重試策略,RetryOneTime:超時重連僅僅一次 .retryPolicy(new RetryOneTime(3000))//命名空間,父節點,若是不指定是在根節點下 .namespace("node4") .build();//啓動client.start();

重連策略

會話鏈接策略,便是當客戶端與 Zookeeper 斷開鏈接以後,客戶端從新鏈接 Zookeeper 時使用的策略,好比從新鏈接一次。

RetryOneTime:N 秒後重連一次,僅僅一次,演示以下:

.retryPolicy(new RetryOneTime(3000))
RetryNTimes:每 n 秒重連一次,重連 m 次。演示以下://每三秒重連一次,重連3次。arg1:多長時間後重連,單位毫秒,arg2:總共重連幾回.retryPolicy(new RetryNTimes(3000,3))
RetryUntilElapsed:設置了最大等待時間,若是超過這個最大等待時間將會再也不鏈接。//每三秒重連一次,等待時間超過10秒再也不重連。arg1:總等待時間,arg2:多長時間重連,單位毫秒.retryPolicy(new RetryUntilElapsed(10000,3000))

新增節點

新增節點

client.create()//指定節點的類型。PERSISTENT:持久化節點,PERSISTENT_SEQUENTIAL:持久化有序節點,EPHEMERAL:臨時節點,EPHEMERAL_SEQUENTIAL臨時有序節點 .withMode(CreateMode.PERSISTENT)//指定權限列表,OPEN_ACL_UNSAFE:world:anyone:crdwa .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//寫入節點數據,arg1:節點名稱 arg2:節點數據 .forPath("/a", "a".getBytes());
自定義權限列表:withACL(acls)方法中能夠設置自定義的權限列表,代碼以下://自定義權限列表List acls=new ArrayList<>();//指定受權模式和受權對象 arg1:受權模式,arg2受權對象Id id=new Id("ip","127.0.0.1");//指定授予的權限,ZooDefs.Perms.ALL:crdwaacls.add(new ACL(ZooDefs.Perms.ALL,id));client.create() .withMode(CreateMode.PERSISTENT)//指定自定義權限列表 .withACL(acls) .forPath("/b", "b".getBytes());
遞歸建立節點:creatingParentsIfNeeded()方法用於建立多層節點,若是其中一個節點不存在則會自動建立//遞歸建立節點client.create()//遞歸方法,若是節點不存在,那麼建立該節點 .creatingParentsIfNeeded() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//test節點和b節點不存在,遞歸建立出來 .forPath("/test/a", "a".getBytes());
異步建立節點:inBackground()方法能夠異步回調建立節點,建立完成後會自動回調實現的方法//異步建立節點client.create() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//異步建立 .inBackground(new BackgroundCallback() {/** * @param curatorFramework 客戶端對象 * @param curatorEvent 事件對象 */@Overridepublic void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {//打印事件類型 System.out.println(curatorEvent.getType()); } }) .forPath("/test1", "a".getBytes());

更新節點數據

更新節點,當節點不存在會報錯,代碼以下:

client.setData() .forPath("/a","a".getBytes());

攜帶版本號更新節點,當版本錯誤拒絕更新

client.setData()//指定版本號更新,若是版本號錯誤則拒絕更新 .withVersion(1) .forPath("/a","a".getBytes());

異步更新節點數據:

client.setData()//異步更新 .inBackground(new BackgroundCallback() {//回調方法@Overridepublic void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception { } }) .forPath("/a","a".getBytes());

刪除節點

刪除當前節點,若是有子節點則拒絕刪除client.delete()//刪除節點,若是是該節點包含子節點,那麼不能刪除 .forPath("/a");
指定版本號刪除,若是版本錯誤則拒絕刪除client.delete()//指定版本號刪除 .withVersion(1)//刪除節點,若是是該節點包含子節點,那麼不能刪除 .forPath("/a");
若是當前節點包含子節點則一併刪除,使用deletingChildrenIfNeeded()方法client.delete()//若是刪除的節點包含子節點則一塊兒刪除 .deletingChildrenIfNeeded()//刪除節點,若是是該節點包含子節點,那麼不能刪除 .forPath("/a");
異步刪除節點,使用inBackground()client.delete() .deletingChildrenIfNeeded()//異步刪除節點 .inBackground(new BackgroundCallback() { @Overridepublic void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {//回調監聽 } })//刪除節點,若是是該節點包含子節點,那麼不能刪除 .forPath("/a");

獲取節點數據

同步獲取節點數據byte[] bytes = client.getData().forPath("/node1");System.out.println(new String(bytes));
獲取節點狀態和數據//保存節點狀態Stat stat=new Stat();byte[] bytes = client.getData()//獲取節點狀態存儲在stat對象中 .storingStatIn(stat) .forPath("/node1");System.out.println(new String(bytes));//獲取節點數據的長度System.out.println(stat.getDataLength());

異步獲取節點數據

client.getData()//異步獲取節點數據,回調監聽 .inBackground((curatorFramework, curatorEvent) -> {//節點數據 System.out.println(new String(curatorEvent.getData())); }) .forPath("/node1");

獲取子節點

同步獲取所有子節點 List strs = client.getChildren().forPath("/");for (String str:strs) { System.out.println(str); }
異步獲取所有子節點client.getChildren()//異步獲取.inBackground((curatorFramework, curatorEvent) -> { List strs = curatorEvent.getChildren();for (String str:strs) { System.out.println(str); } }).forPath("/");查看節點是否存在


同步查看

//若是節點不存在,stat爲nullStat stat = client.checkExists().forPath("/node");
異步查看//若是節點不存在,stat爲nullclient.checkExists() .inBackground((curatorFramework, curatorEvent) -> {//若是爲null則不存在 System.out.println(curatorEvent.getStat()); }) .forPath("/node");

Watcher API

curator 提供了兩種 watcher 來監聽節點的變化

NodeCache:監聽一個特定的節點,監聽新增和修改

PathChildrenCache:監聽一個節點的子節點,當一個子節點增長、刪除、更新時,path Cache 會改變他的狀態,會包含最新的子節點的數據和狀態。

NodeCache 演示:

//arg1:鏈接對象 arg2:監聽的節點路徑,/namespace/pathfinal NodeCache nodeCache = new NodeCache(client, "/w1");//啓動監聽nodeCache.start();//添加監聽器nodeCache.getListenable().addListener(() -> {//節點路徑 System.out.println(nodeCache.getCurrentData().getPath());//節點數據 System.out.println(new String(nodeCache.getCurrentData().getData()));});//睡眠100秒Thread.sleep(1000000);//關閉監聽nodeCache.close();
PathChildrenCache演示://arg1:鏈接對象 arg2:節點路徑 arg3:是否可以獲取節點數據PathChildrenCache cache=new PathChildrenCache(client,"/w1", true);cache.start();cache.getListenable().addListener((curatorFramework, pathChildrenCacheEvent) -> {//節點路徑 System.out.println(pathChildrenCacheEvent.getData().getPath());//節點狀態 System.out.println(pathChildrenCacheEvent.getData().getStat());//節點數據 System.out.println(new String(pathChildrenCacheEvent.getData().getData()));});cache.close();

小福利

是否是以爲文章太長看得頭暈腦脹,爲此陳某特意將本篇文章製做成 PDF 文本,須要回去仔細研究的朋友,老規矩,回覆關鍵詞ZK入門指南。

本文分享自微信公衆號 - 碼農沉思錄(code-thinker)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索