萬字長文帶你入門Zookeeper!!!
阿新 • • 發佈:2020-04-11
導讀
- 文章首發於微信公眾號【碼猿技術專欄】,原創不易,謝謝支援。
- Zookeeper 相信大家都聽說過,最典型的使用就是作為服務註冊中心。今天陳某帶大家從零基礎入門 Zookeeper,看了本文,你將會對 Zookeeper 有了初步的瞭解和認識。
- 注意:本文基於 Zookeeper 的版本是 3.4.14,最新版本的在使用上會有一些出入,但是企業現在使用的大部分都是 3.4x 版本的。
Zookeeper 概述
- Zookeeper 是一個分散式協調服務的開源框架。主要用來解決分散式叢集中應用系統的一致性問題,例如怎樣避免同時操作同一資料造成髒讀的問題。
- ZooKeeper 本質上是一個分散式的小檔案儲存系統。提供基於類似於檔案系 統的目錄樹方式的資料儲存,並且可以對樹中的節點進行有效管理。從而用來維護和監控你儲存的資料的狀態變化。通過監控這些資料狀態的變化,從而可以達 到基於資料的叢集管理。諸如:
統一命名服務
分散式配置管理
、分散式訊息佇列
、分散式鎖
、分散式協調
等功能。
Zookeeper 特性
-
全域性資料一致
:每個 server 儲存一份相同的資料副本,client 無論連 接到哪個 server,展示的資料都是一致的,這是最重要的特徵; -
可靠性
:如果訊息被其中一臺伺服器接受,那麼將被所有的伺服器接受。 -
順序性
:包括全域性有序和偏序兩種:全域性有序是指如果在一臺伺服器上 訊息 a 在訊息 b 前釋出,則在所有 Server 上訊息 a 都將在訊息 b 前被 釋出;偏序是指如果一個訊息 b 在訊息 a 後被同一個傳送者釋出,a 必將排在 b 前面。 -
資料更新原子性
:一次資料更新要麼成功(半數以上節點成功),要麼失 敗,不存在中間狀態; -
實時性
:Zookeeper 保證客戶端將在一個時間間隔範圍內獲得伺服器的更新資訊,或者伺服器失效的資訊。
Zookeeper 節點型別
- Znode 有兩種,分別為臨時節點和永久節點。
臨時節點
:該節點的生命週期依賴於建立它們的會話。一旦會話結束,臨時節點將被自動刪除,當然可以也可以手動刪除。臨時節點不允許擁有子節點。永久節點
:該節點的生命週期不依賴於會話,並且只有在客戶端顯示執行刪除操作的時候,他們才能被刪除。
- 節點的型別在建立時即被確定,並且不能改變。
- Znode 還有一個序列化的特性,如果建立的時候指定的話,該 Znode 的名字後面會自動追加一個不斷增加的序列號。序列號對於此節點的父節點來說是唯一的,這樣便會記錄每個子節點建立的先後順序。它的格式為
"%10d"
- 這樣便會存在四種類型的 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
- 建立持久化有序節點:
## 完整的節點名稱是a0000000001
create /a a
Created /a0000000001
## 完整的節點名稱是b0000000002
create /b b
Created /b0000000002
- 建立臨時節點:
create -e /a a
- 建立臨時有序節點:
## 完整的節點名稱是a0000000001
create -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 = 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
檢視節點列表
- 檢視節點列表有
ls path
和ls2 path
兩個命令。後者是前者的增強,不僅會返回節點列表還會返回當前節點的狀態資訊。 ls path
:
ls /
## 僅僅返回節點列表
[zookeeper, node1]
ls2 path
:
ls2 /
## 返回節點列表和當前節點的狀態資訊
[zookeeper, node1]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x6
cversion = 2
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 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
## 刪除節點node2
delete /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]
:設定 ACLaddauth <scheme> <auth>
:新增認證使用者,和 auth,digest 授權模式相關。
world 授權模式案例
- zookeeper 中預設的授權模式,針對登入 zookeeper 的任何使用者授予指定的許可權。命令如下:
setAcl [path] world:anyone:[permission]
path
:節點permission
:授予的許可權,比如cdrwa
- 去掉不能讀取節點資料的許可權:
## 獲取許可權列表(預設的)
getAcl /node2
'world,'anyone
: cdrwa
## 去掉讀取節點資料的的許可權,去掉r
setAcl /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 客戶端的開發量。
新增依賴
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
建立連線
- 客戶端建立與 Zookeeper 的連線,這裡僅僅演示單機版本的連線,如下:
//建立CuratorFramework,用來操作api
CuratorFramework 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<ACL> acls=new ArrayList<>();
//指定授權模式和授權物件 arg1:授權模式,arg2授權物件
Id id=new Id("ip","127.0.0.1");
//指定授予的許可權,ZooDefs.Perms.ALL:crdwa
acls.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 事件物件
*/
@Override
public 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() {
//回撥方法
@Override
public 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() {
@Override
public 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<String> strs = client.getChildren().forPath("/");
for (String str:strs) {
System.out.println(str);
}
- 非同步獲取全部子節點
client.getChildren()
//非同步獲取
.inBackground((curatorFramework, curatorEvent) -> {
List<String> strs = curatorEvent.getChildren();
for (String str:strs) {
System.out.println(str);
}
})
.forPath("/");
檢視節點是否存在
- 同步檢視
//如果節點不存在,stat為null
Stat stat = client.checkExists().forPath("/node");
- 非同步檢視
//如果節點不存在,stat為null
client.checkExists()
.inBackground((curatorFramework, curatorEvent) -> {
//如果為null則不存在
System.out.println(curatorEvent.getStat());
})
.forPath("/node");
Watcher API
- curator 提供了兩種 watcher 來監聽節點的變化
NodeCache
:監聽一個特定的節點,監聽新增和修改PathChildrenCache
:監聽一個節點的子節點,當一個子節點增加、刪除、更新時,path Cache 會改變他的狀態,會包含最新的子節點的資料和狀態。
- NodeCache 演示:
//arg1:連線物件 arg2:監聽的節點路徑,/namespace/path
final 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入門指南
。