ZooKeeper是一個高可用的分佈式數據管理與系統協調框架
。基於對Paxos算法的實現,使該框架保證了分佈式環境中數據的強一致性
,也正是基於這樣的特性,使得ZooKeeper解決不少分佈式問題。網上對ZK的應用場景也有很多介紹,本文將介紹比較經常使用的項目例子,系統地對ZK的應用場景進行一個分門歸類的介紹。node
值得注意的是,ZK並不是天生就是爲這些應用場景設計的,都是後來衆多開發者根據其框架的特性
,利用其提供的一系列API接口(或者稱爲原語集),摸索出來的典型使用方法
。所以,也很是歡迎讀者分享你在ZK使用上的奇技淫巧。算法
Zookeeper 會維護一個具備層次關係的數據結構
,它很是相似於一個標準的文件系統,如圖所示:數據庫
圖中的每一個節點稱爲一個znode. 每一個znode由3部分組成:apache
stat. 此爲狀態信息, 描述該znode的版本, 權限等信息
;設計模式
data. 與該znode關聯的數據
;api
children. 該znode下的子節點
;服務器
Zookeeper 這種數據結構有以下這些特色:網絡
每一個子目錄項如 NameService 都被稱做爲 znode,這個 znode 是被它所在的路徑惟一標識,如 Server1 這個 znode 的標識爲 /NameService/Server1;session
znode 能夠有子節點目錄,而且每一個 znode 能夠存儲數據,注意 EPHEMERAL 類型的目錄節點不能有子節點目錄
;數據結構
znode 是有版本的,每一個 znode 中存儲的數據能夠有多個版本,也就是一個訪問路徑中能夠存儲多份數據
;
znode 能夠是臨時節點,一旦建立這個 znode 的客戶端與服務器失去聯繫,這個 znode 也將自動刪除
,Zookeeper 的客戶端和服務器通訊採用長鏈接方式,每一個客戶端和服務器經過心跳來保持鏈接
,這個鏈接狀態稱爲 session,若是 znode 是臨時節點,這個 session 失效,znode 也就刪除了;
znode 的目錄名能夠自動編號
,如 App1 已經存在,再建立的話,將會自動命名爲 App2;
znode 能夠被監控,包括這個目錄節點中存儲的數據的修改,子節點目錄的變化等
,一旦變化能夠通知設置監控的客戶端,這個是 Zookeeper 的核心特性
,Zookeeper 的不少功能都是基於這個特性實現的,後面在典型的應用場景中會有實例介紹;
znode節點的狀態信息:
使用get命令獲取指定節點的數據時, 同時也將返回該節點的狀態信息, 稱爲Stat
. 其包含以下字段:
czxid. 節點建立時的zxid;
mzxid. 節點最新一次更新發生時的zxid;
ctime. 節點建立時的時間戳;
mtime. 節點最新一次更新發生時的時間戳;
dataVersion. 節點數據的更新次數;
cversion. 其子節點的更新次數;
aclVersion. 節點ACL(受權信息)的更新次數;
ephemeralOwner. 若是該節點爲ephemeral節點, ephemeralOwner值表示與該節點綁定的session id. 若是該節點不是 ephemeral節點, ephemeralOwner值爲0. 至於什麼是ephemeral節點;
dataLength. 節點數據的字節數;
numChildren. 子節點個數;
zxid:
znode節點的狀態信息中包含czxid和mzxid, 那麼什麼是zxid呢?
ZooKeeper狀態的每一次改變, 都對應着一個遞增的Transaction id, 該id稱爲zxid
. 因爲zxid的遞增性質, 若是zxid1小於zxid2, 那麼zxid1確定先於zxid2發生. 建立任意節點, 或者更新任意節點的數據, 或者刪除任意節點, 都會致使Zookeeper狀態發生改變, 從而致使zxid的值增長
.
session:
在client和server通訊以前, 首先須要創建鏈接, 該鏈接稱爲session. 鏈接創建後, 若是發生鏈接超時, 受權失敗, 或者顯式關閉鏈接, 鏈接便處於CLOSED狀態, 此時session結束.
節點類型:
講述節點狀態的ephemeralOwner字段時, 提到過有的節點是ephemeral節點, 而有的並非. 那麼節點都具備哪些類型呢? 每種類型的節點又具備哪些特色呢?
persistent. persistent節點不和特定的session綁定
, 不會隨着建立該節點的session的結束而消失, 而是一直存在, 除非該節點被顯式刪除.
ephemeral. ephemeral(臨時)節點是臨時性的, 若是建立該節點的session結束了, 該節點就會被自動刪除
. ephemeral節點不能擁有子節點
. 雖然ephemeral節點與建立它的session綁定, 但只要該節點沒有被刪除, 其餘session就能夠讀寫該節點中關聯的數據. 使用-e參數指定建立ephemeral節點
.
create -e /xing/ei world
sequence. 嚴格的說, sequence(順序)並不是節點類型中的一種
. sequence節點既能夠是ephemeral的, 也能夠是persistent的. 建立sequence節點時, ZooKeeper server會在指定的節點名稱後加上一個數字序列, 該數字序列是遞增的
. 所以能夠屢次建立相同的sequence節點, 而獲得不一樣的節點. 使用-s參數指定建立sequence節點
.
[zk: localhost:4180(CONNECTED) 0] create -s /xing/item world
Created /xing/item0000000001
[zk: localhost:4180(CONNECTED) 1] create -s /xing/item world
Created /xing/item0000000002
[zk: localhost:4180(CONNECTED) 2] create -s /xing/item world
Created /xing/item0000000003
[zk: localhost:4180(CONNECTED) 3] create -s /xing/item world
Created /xing/item0000000004
watch:
watch的意思是監聽感興趣的事件
. 在命令行中, 如下幾個命令能夠指定是否監聽相應的事件.
ls命令. ls命令的第一個參數指定znode, 第二個參數若是爲true, 則說明監聽該znode的子節點的增減, 以及該znode自己的刪除事件.
[zk: localhost:4180(CONNECTED) 21] ls /xing true
[]
[zk: localhost:4180(CONNECTED) 22] create /xing/item item000
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/xing
Created /xing/item
get命令. get命令的第一個參數指定znode, 第二個參數若是爲true, 則說明監聽該znode的更新和刪除事件
.
[zk: localhost:4180(CONNECTED) 39] get /xing true
world
cZxid = 0x100000066
ctime = Fri May 17 22:30:01 CST 2013
mZxid = 0x100000066
mtime = Fri May 17 22:30:01 CST 2013
pZxid = 0x100000066
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:4180(CONNECTED) 40] create /xing/item item000
Created /xing/item
[zk: localhost:4180(CONNECTED) 41] rmr /xing
WATCHER::
WatchedEvent state:SyncConnected type:NodeDeleted path:/xing
Zookeeper 做爲一個分佈式的服務框架,主要用來解決分佈式集羣中應用系統的一致性問題
,它能提供基於相似於文件系統的目錄節點樹方式的數據存儲,可是 Zookeeper 並非用來專門存儲數據的,它的做用主要是用來維護和監控你存儲的數據的狀態變化
。經過監控這些數據狀態的變化,從而能夠達到基於數據的集羣管理
,後面將會詳細介紹 Zookeeper 可以解決的一些典型問題,這裏先介紹一下,Zookeeper 的操做接口和簡單使用示例。
客戶端要鏈接 Zookeeper 服務器能夠經過建立 org.apache.zookeeper.ZooKeeper
的一個實例對象,而後調用這個類提供的接口來和服務器交互。
前面說了 ZooKeeper 主要是用來維護和監控一個目錄節點樹中存儲的數據的狀態
,全部咱們可以操做 ZooKeeper 的也和操做目錄節點樹大致同樣,如建立一個目錄節點,給某個目錄節點設置數據,獲取某個目錄節點的全部子目錄節點,給某個目錄節點設置權限和監控這個目錄節點的狀態變化。
ZooKeeper 基本的操做示例:
public class ZkDemo {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
// 建立一個與服務器的鏈接
ZooKeeper zk = new ZooKeeper("127.0.0.1:2180", 60000, new Watcher() {
// 監控全部被觸發的事件
// 當對目錄節點監控狀態打開時,一旦目錄節點的狀態發生變化,Watcher 對象的 process 方法就會被調用。
public void process(WatchedEvent event) {
System.out.println("EVENT:" + event.getType());
}
});
// 查看根節點
// 獲取指定 path 下的全部子目錄節點,一樣 getChildren方法也有一個重載方法能夠設置特定的 watcher 監控子節點的狀態
System.out.println("ls / => " + zk.getChildren("/", true));
// 判斷某個 path 是否存在,並設置是否監控這個目錄節點,這裏的 watcher 是在建立 ZooKeeper 實例時指定的 watcher;
// exists方法還有一個重載方法,能夠指定特定的 watcher
if (zk.exists("/node", true) == null) {
// 建立一個給定的目錄節點 path, 並給它設置數據;
// CreateMode 標識有四種形式的目錄節點,分別是:
// PERSISTENT:持久化目錄節點,這個目錄節點存儲的數據不會丟失;
// PERSISTENT_SEQUENTIAL:順序自動編號的目錄節點,這種目錄節點會根據當前已近存在的節點數自動加 1,而後返回給客戶端已經成功建立的目錄節點名;
// EPHEMERAL:臨時目錄節點,一旦建立這個節點的客戶端與服務器端口也就是 session 超時,這種節點會被自動刪除;
// EPHEMERAL_SEQUENTIAL:臨時自動編號節點
zk.create("/node", "conan".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("create /node conan");
// 查看/node節點數據
System.out.println("get /node => " + new String(zk.getData("/node", false, null)));
// 查看根節點
System.out.println("ls / => " + zk.getChildren("/", true));
}
// 建立一個子目錄節點
if (zk.exists("/node/sub1", true) == null) {
zk.create("/node/sub1", "sub1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("create /node/sub1 sub1");
// 查看node節點
System.out.println("ls /node => " + zk.getChildren("/node", true));
}
// 修改節點數據
if (zk.exists("/node", true) != null) {
// 給 path 設置數據,能夠指定這個數據的版本號,若是 version 爲 -1 怎能夠匹配任何版本
zk.setData("/node", "changed".getBytes(), -1);
// 查看/node節點數據
// 獲取這個 path 對應的目錄節點存儲的數據,數據的版本等信息能夠經過 stat 來指定,同時還能夠設置是否監控這個目錄節點數據的狀態
System.out.println("get /node => " + new String(zk.getData("/node", false, null)));
}
// 刪除節點
if (zk.exists("/node/sub1", true) != null) {
// 刪除 path 對應的目錄節點,version 爲 -1 能夠匹配任何版本,也就刪除了這個目錄節點全部數據
zk.delete("/node/sub1", -1);
zk.delete("/node", -1);
// 查看根節點
System.out.println("ls / => " + zk.getChildren("/", true));
}