ZooKeeper 常用操作API詳解
轉載:http://www.aboutyun.com/thread-12817-1-1.html
問題導讀:
1、ZooKeeper的常用API方法都有那些?
2、Zookeeper 到底能幫我們解決那些問題?
3、如何實現共享鎖(Locks)?
ZooKeeper是一個用於分散式應用程式的分散式開源協調服務。它使用一組簡單的操作原語,使得分散式應用可以實現更高層次的服務——如同步、配置維護、群組和命名管理等。ZK具有高效能、高可用(複製)、有序等特徵。請參考上一篇譯文zookeeper:一個用於分散式應用的分散式協調服務。本文簡單介紹一下開發中經常使用的方法(文件:ZooKeeper API)。
1. create
Class:org.apache.zookeeper.ZooKeeper
-
public String create(String path, byte[] data, List acl, CreateMode createMode) throws KeeperException, InterruptedException
- public void create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.StringCallback cb, Object ctx)
複製程式碼
建立一個給定路徑(path)的節點,並給它設定資料(data)和訪問控制列表(acl)。ZooKeeper中的節點相對於檔案系統中的目錄結構,即是”directory”又是”regular file”。第二個create方法是create的非同步版本,當建立完成時則呼叫非同步callback。
(1). org.apache.zookeeper.data.ACL
這部分節選說說Zookeeper中的ACL
ZooKeeper通過ACL來對ZNode進行訪問控制。ZooKeeper客戶端為znode指定ACL列表,ZooKeeper伺服器根據ACL列表判定某個請求ZNode的客戶端是否有對應操作的許可權。
一個ACL物件由schema:ID和Permissions組成。
a). scheme: scheme對應於採用哪種方案來進行許可權管理,zookeeper實現了一個pluggable的ACL方案,可以通過擴充套件scheme,來擴充套件ACL的機制。zookeeper-3.4.4預設支援下面幾種scheme:
* world: 它下面只有一個id, 叫anyone, world:anyone代表任何人,zookeeper中對所有人有許可權的結點就是屬於world:anyone的
* auth: 它不需要id, 只要是通過authentication的user都有許可權(zookeeper支援通過kerberos來進行authencation, 也支援username/password形式的authentication)
* digest: 它對應的id為username:BASE64(SHA1(password)),它需要先通過username:password形式的authentication
* ip: 它對應的id為客戶機的IP地址,設定的時候可以設定一個ip段,比如ip:192.168.1.0/16, 表示匹配前16個bit的IP段
* super: 在這種scheme情況下,對應的id擁有超級許可權,可以做任何事情(cdrwa)
b). perm. ZooKeeper中有5種許可權,從低位到高位分別是READ、WRITE、CREATE、DELETE和ADMIN,ACL的Permissions可以是5種許可權中的1種或多種,它們的含義是:
* READ: 允許獲取該節點的值和列出子節點。
* WRITE: 允許設定該節點的值。
* CREATE: 允許建立子節點。
* DELETE: 可以刪除子節點。
* ADMIN: 允許為該節點設定許可權。
(2). org.apache.zookeeper.CreateMode
org.apache.zookeeper.CreateMode可以設定znode是否為EPHEMERAL或者SEQUENTIAL。可以為下面四種值:
PERSISTENT 持久化目錄znode
PERSISTENT_SEQUENTIAL 順序自動編號的目錄znode。這個目錄節點是根據當前已存在的節點數遞增。
EPHEMERAL 臨時目錄znode,一旦建立這個znode的客戶端和伺服器斷開,這個節點就會自動刪除。臨時節點(EPHEMERAL)不能有子節點資料
EPHEMERAL_SEQUENTIAL 臨時自動編號znode。
(3). zkCli命令
在zkCli中實現了create的命令封裝,可以使用者測試和資料管理:
- create [-s] [-e] path data acl
複製程式碼
其中”-s”表示建立一個順序自動編號的節點,”-e”表示建立一個臨時節點.預設為永續性節點
例如:
建立一個永久節點和臨時節點
-
create /test null
-
Created /test
-
create -e /test0 null
- Created /test0
複製程式碼
建立一個順序自動編號的節點,ACL為使用digest(使用者名稱:test 密碼:debugo),許可權為所有(rwcda)。關於digest的產生,可以參考zookeeper中 DigestAuthenticationProvider.generateDigest(String ipName)方法;通過向此方法指定原始的使用者名稱和密碼即可獲得”digest”之後的字串,比如傳入”test:test”,將會得
到”test:V28q/NynI4JI3Rk54h0r8O5kMug=”,其內部原理是將”密碼”部分進行MD5 + sha1操作.
-
create -s /test0/test null digest:test:V28q/NynI4JI3Rk54h0r8O5kMug=:rwcda
-
Ephemerals cannot have children: /test0/test
-
create -s /test/test null digest:test:V28q/NynI4JI3Rk54h0r8O5kMug=:rwcda
- Created /test/test0000000000
複製程式碼
建立一個節點,其ACL使用ip(172.19.17.0/24)只具有讀許可權
-
create /test/test1 “hello world” ip:172.19.17.0/24:r
- Created /test/test1
複製程式碼
2. exist
class:org.apache.zookeeper.ZooKeeper
- public Stat exists(String path, Watcher watcher) throws KeeperException, InterruptedException
複製程式碼
返回某個path的znode是否存在。並設定是否監控這個節點(第二個引數boolean watcher)。當第二個引數為true且這個語句執行成功時,監聽器(watcher)就會在成功執行建立節點/刪除節點/修改該節點資料時候被觸發。
- exists(String, Watcher)
複製程式碼
過載方法,這裡可以指定特定的監聽器(watcher)物件。
-
exists(String, Watcher, AsyncCallback.StatCallback, Object)
- exists(String, boolean, AsyncCallback.StatCallback, Object)
複製程式碼
exist的非同步實現
3. delete
- public void delete(String path, int version) throws InterruptedException, KeeperException
複製程式碼
刪除path對應的znode,version為-1可以匹配任何版本,也就是刪除這個節點所有的資料。此外,delete同樣存在非同步版本。
- delete(String path, int version, AsyncCallback.VoidCallback cb, Object ctx)
複製程式碼
delete的非同步版本。
例如zkCli中刪除某個節點:
- delete /test/test1
複製程式碼
4. getChildren
- public List getChildren(String path, boolean watch) throws KeeperException, InterruptedException
複製程式碼
獲取指定path下所有的子znode,這個方法和exist一樣同樣可以設定watcher/指定特定的Watcher物件。
5. setData & getData
- Stat setData(String path, byte[] data, int version)
複製程式碼
當給定path的節點存在時給path設定資料,可以指定這個資料的版本號。如果version為-1則可以匹配任意版本。
- void setData(String path, byte[] data, int version, AsyncCallback.StatCallback cb, Object ctx)
複製程式碼
setData的非同步版本。
byte[] getData(String path, Watcher watcher, Stat stat)
獲取這個path對應的znode節點的資料,資料的版本等資訊可以通過stat來指定。
void getData(String path, Watcher watcher, AsyncCallback.DataCallback cb, Object ctx)
getData的非同步版本。
6. setACL和getACL
Stat setACL(String path, List acl, int version)
給某個znode節點重新設定訪問許可權,需要注意的是ZooKeeper中的目錄節點許可權都不具有傳遞性,父znode節點的許可權不能傳遞給子目錄節點。在create中已經介紹了ACL的設定方法,可以設定一系列ACL規則(即指定一系列ACL物件)。
void setACL(String path, List acl, int version, AsyncCallback.StatCallback cb, Object ctx)
setACL的非同步版本
List getACL(String path, Stat stat)
返回某個znode節點的ACL物件的列表。
void getACL(String path, Stat stat, AsyncCallback.ACLCallback cb, Object ctx)
getACL的非同步版本
例如zkCli中設定某個ACL規則:
-
[zk: localhost:2181(CONNECTED) 43] setAcl /test world:anyone:r
-
cZxid = 0xf000500ed
-
ctime = Wed Sep 24 15:13:29 CST 2014
-
……
-
[zk: localhost:2181(CONNECTED) 44] getAcl /test
-
‘world,’anyone
- : r
複製程式碼
下面的內容摘自http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/,寫得很好,轉過來自己學習一下。
ZooKeeper 典型的應用場景
Zookeeper 從設計模式角度來看,是一個基於觀察者模式設計的分散式服務管理框架,它負責儲存和管理大家都關心的資料,然後接受觀察者的註冊,一旦這些資料的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上註冊的那些觀察者做出相應的反應,從而實現叢集中類似
Master/Slave 管理模式,關於 Zookeeper 的詳細架構等內部細節可以閱讀 Zookeeper 的原始碼
下面詳細介紹這些典型的應用場景,也就是 Zookeeper 到底能幫我們解決那些問題?下面將給出答案。
統一命名服務(Name Service)
分散式應用中,通常需要有一套完整的命名規則,既能夠產生唯一的名稱又便於人識別和記住,通常情況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裡你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service
與 JNDI 能夠完成的功能是差不多的,它們都是將有層次的目錄結構關聯到一定資源上,但是 Zookeeper 的 Name Service 更加是廣泛意義上的關聯,也許你並不需要將名稱關聯到特定資源上,你可能只需要一個不會重複名稱,就像資料庫中產生一個唯一的數字主鍵一樣。
Name Service 已經是 Zookeeper 內建的功能,你只要呼叫 Zookeeper 的 API 就能實現。如呼叫 create 介面就可以很容易建立一個目錄節點。
配置管理(Configuration Management)
配置的管理在分散式應用環境中很常見,例如同一個應用系統需要多臺 PC Server 執行,但是它們執行的應用系統的某些配置項是相同的,如果要修改這些相同的配置項,那麼就必須同時修改每臺執行這個應用系統的 PC Server,這樣非常麻煩而且容易出錯。
像這樣的配置資訊完全可以交給 Zookeeper 來管理,將配置資訊儲存在 Zookeeper 的某個目錄節點中,然後將所有需要修改的應用機器監控配置資訊的狀態,一旦配置資訊發生變化,每臺應用機器就會收到 Zookeeper 的通知,然後從 Zookeeper 獲取新的配置資訊應用到系統中。
圖 2. 配置管理結構圖
叢集管理(Group Membership)
Zookeeper 能夠很容易的實現叢集管理的功能,如有多臺 Server 組成一個服務叢集,那麼必須要一個“總管”知道當前叢集中每臺機器的服務狀態,一旦有機器不能提供服務,叢集中其它叢集必須知道,從而做出調整重新分配服務策略。同樣當增加叢集的服務能力時,就會增加一臺或多臺
Server,同樣也必須讓“總管”知道。
Zookeeper 不僅能夠幫你維護當前的叢集中機器的服務狀態,而且能夠幫你選出一個“總管”,讓這個總管來管理叢集,這就是 Zookeeper 的另一個功能 Leader Election。
它們的實現方式都是在 Zookeeper 上建立一個 EPHEMERAL 型別的目錄節點,然後每個 Server 在它們建立目錄節點的父目錄節點上呼叫 getChildren(String
path, boolean watch) 方法並設定 watch 為 true,由於是 EPHEMERAL 目錄節點,當建立它的 Server 死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時 getChildren上的
Watch 將會被呼叫,所以其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是同樣的原理。
Zookeeper 如何實現 Leader Election,也就是選出一個 Master Server。和前面的一樣每臺 Server 建立一個 EPHEMERAL 目錄節點,不同的是它還是一個 SEQUENTIAL 目錄節點,所以它是個 EPHEMERAL_SEQUENTIAL
目錄節點。之所以它是 EPHEMERAL_SEQUENTIAL 目錄節點,是因為我們可以給每臺 Server 編號,我們可以選擇當前是最小編號的 Server 為 Master,假如這個最小編號的 Server 死去,由於是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,所以當前的節點列表中又出現一個最小編號的節點,我們就選擇這個節點為當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。
圖 3. 叢集管理結構圖
這部分的示例程式碼如下,完整的程式碼請看附件:
清單 3. Leader Election 關鍵程式碼
-
void findLeader() throws InterruptedException {
-
byte[] leader = null;
-
try {
-
leader = zk.getData(root + “/leader”, true, null);
-
} catch (Exception e) {
-
logger.error(e);
-
}
-
if (leader != null) {
-
following();
-
} else {
-
String newLeader = null;
-
try {
-
byte[] localhost = InetAddress.getLocalHost().getAddress();
-
newLeader = zk.create(root + “/leader”, localhost,
-
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
-
} catch (Exception e) {
-
logger.error(e);
-
}
-
if (newLeader != null) {
-
leading();
-
} else {
-
mutex.wait();
-
}
-
}
- }
複製程式碼
共享鎖(Locks)
共享鎖在同一個程序中很容易實現,但是在跨程序或者在不同 Server 之間就不好實現了。Zookeeper 卻很容易實現這個功能,實現方式也是需要獲得鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,然後呼叫 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己建立的目錄節點,如果正是自己建立的,那麼它就獲得了這個鎖,如果不是那麼它就呼叫
exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到自己建立的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所建立的目錄節點就行了。
圖 4. Zookeeper 實現 Locks 的流程圖
同步鎖的實現程式碼如下,完整的程式碼請看附件:
清單 4. 同步鎖的關鍵程式碼
-
void getLock() throws KeeperException, InterruptedException{
-
List<String> list = zk.getChildren(root, false);
-
String[] nodes = list.toArray(new String[list.size()]);
-
Arrays.sort(nodes);
-
if(myZnode.equals(root+”/”+nodes[0])){
-
doAction();
-
}
-
else{
-
waitForLock(nodes[0]);
-
}
-
}
-
void waitForLock(String lower) throws InterruptedException, KeeperException {
-
Stat stat = zk.exists(root + “/” + lower,true);
-
if(stat != null){
-
mutex.wait();
-
}
-
else{
-
getLock();
-
}
- }
複製程式碼
佇列管理
Zookeeper 可以處理兩種型別的佇列:
當一個佇列的成員都聚齊時,這個佇列才可用,否則一直等待所有成員到達,這種是同步佇列。
佇列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。
同步佇列用 Zookeeper 實現的實現思路如下:
建立一個父目錄 /synchronizing,每個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,然後每個成員都加入這個佇列,加入佇列的方式就是建立 /synchronizing/member_i 的臨時目錄節點,然後每個成員獲取
/ synchronizing 目錄的所有目錄節點,也就是 member_i。判斷 i 的值是否已經是成員的個數,如果小於成員個數等待 /synchronizing/start 的出現,如果已經相等就建立 /synchronizing/start。
用下面的流程圖更容易理解:
圖 5. 同步佇列流程圖
同步佇列的關鍵程式碼如下,完整的程式碼請看附件:
清單 5. 同步佇列
-
void addQueue() throws KeeperException, InterruptedException{
-
zk.exists(root + “/start”,true);
-
zk.create(root + “/” + name, new byte[0], Ids.OPEN_ACL_UNSAFE,
-
CreateMode.EPHEMERAL_SEQUENTIAL);
-
synchronized (mutex) {
-
List<String> list = zk.getChildren(root, false);
-
if (list.size() < size) {
-
mutex.wait();
-
} else {
-
zk.create(root + “/start”, new byte[0], Ids.OPEN_ACL_UNSAFE,
-
CreateMode.PERSISTENT);
-
}
-
}
- }
複製程式碼
當佇列沒滿是進入 wait(),然後會一直等待 Watch 的通知,Watch 的程式碼如下:
-
public void process(WatchedEvent event) {
-
if(event.getPath().equals(root + “/start”) &&
-
event.getType() == Event.EventType.NodeCreated){
-
System.out.println(“得到通知”);
-
super.process(event);
-
doAction();
-
}
- }
複製程式碼
FIFO 佇列用 Zookeeper 實現思路如下:
實現的思路也非常簡單,就是在特定的目錄下建立 SEQUENTIAL 型別的子目錄 /queue_i,這樣就能保證所有成員加入佇列時都是有編號的,出佇列時通過 getChildren( ) 方法可以返回當前所有的佇列中的元素,然後消費其中最小的一個,這樣就能保證 FIFO。
下面是生產者和消費者這種佇列形式的示例程式碼,完整的程式碼請看附件:
清單 6. 生產者程式碼
-
boolean produce(int i) throws KeeperException, InterruptedException{
-
ByteBuffer b = ByteBuffer.allocate(4);
-
byte[] value;
-
b.putInt(i);
-
value = b.array();
-
zk.create(root + “/element”, value, ZooDefs.Ids.OPEN_ACL_UNSAFE,
-
CreateMode.PERSISTENT_SEQUENTIAL);
-
return true;
- }
複製程式碼
清單 7. 消費者程式碼
-
int consume() throws KeeperException, InterruptedException{
-
int retvalue = -1;
-
Stat stat = null;
-
while (true) {
-
synchronized (mutex) {
-
List<String> list = zk.getChildren(root, true);
-
if (list.size() == 0) {
-
mutex.wait();
-
} else {
-
Integer min = new Integer(list.get(0).substring(7));
-
for(String s : list){
-
Integer tempValue = new Integer(s.substring(7));
-
if(tempValue < min) min = tempValue;
-
}
-
byte[] b = zk.getData(root + “/element” + min,false, stat);
-
zk.delete(root + “/element” + min, 0);
-
ByteBuffer buffer = ByteBuffer.wrap(b);
-
retvalue = buffer.getInt();
-
return retvalue;
-
}
-
}
-
}
- }
複製程式碼
總結
Zookeeper 作為 Hadoop 專案中的一個子專案,是 Hadoop 叢集管理的一個必不可少的模組,它主要用來控制叢集中的資料,如它管理 Hadoop 叢集中的 NameNode,還有 Hbase 中 Master Election、Server 之間狀態同步等。
本文介紹的 Zookeeper 的基本知識,以及介紹了幾個典型的應用場景。這些都是 Zookeeper 的基本功能,最重要的是 Zoopkeeper 提供了一套很好的分散式叢集管理的機制,就是它這種基於層次型的目錄樹的資料結構,並對樹中的節點進行有效管理,從而可以設計出多種多樣的分散式的資料管理模型,而不僅僅侷限於上面提到的幾個常用應用場景。