ZooKeeper是一個優秀的分佈式協同工具,不少分佈式項目都基於它進行架構設計,不過要想要對其有一個深刻的理解(若是你想閱讀其源代碼),對其客戶端API的熟悉必不可少。下面就簡要記錄一下ZooKeeper中各個API的簡單用法。
這篇文章不打算對ZooKeeper的基本概念及安裝進行講解,想要了解這部份內容能夠參考:http://zookeeper.apache.org/doc/r3.4.3/zookeeperOver.html ,
或者能夠參考:http://zookeeper.apache.org/doc/r3.4.3/zookeeperProgrammers.html
均是官方文檔,這也是想要學習某個開源工具必須的先行步驟,而且官網上的文檔也應該算是最權威的,不過ZooKeeper在這方面的文檔不怎麼多,但做爲入門瞭解,仍是很是有用的。
下面將從基本用法,Watchert的用法,異步調用以及ACL四個方面對ZooKeeper客戶端編程做簡要介紹。
當完成這四個方面的理解之後,就可使用ZK完成一些更加高級的任務,如分佈式鎖、Master選舉、一致性服務保障、配置管理等。官方文檔對此也有簡要介紹,
參考:http://zookeeper.apache.org/doc/r3.4.3/recipes.html
html
基本數據結構java
class Stat { private long czxid; private long mzxid; private long ctime; private long mtime; private int version; private int cversion; private int aversion; private long ephemeralOwner; private int dataLength; private int numChildren; private long pzxid; } class Id { private String scheme; //world、auth、digest、ip private String id; } class ACL { private int perms; //CREATE、READ、WRITE、DELETE、ADMIN private org.apache.zookeeper.data.Id id; }
基本使用
node
try { static String hostport = "127.0.0.1:2181"; ZooKeeper zooKeeper = new ZooKeeper(hostport, 300000, null); //建立一個ZooKeeper實例,不設置默認watcher String path = "/test"; zooKeeper.create(path, path.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //建立一個節點 Stat stat = new Stat(); byte[] b = zooKeeper.getData(path, false, stat); //獲取節點的信息及存儲的數據 System.out.println(stat); System.out.println(new String(b)); stat = zooKeeper.exists(path, false); //查看path所表明的節點是否存在 zooKeeper.setData(path, "helloworld".getBytes(), stat.getVersion()); //設置節點的數據 //zooKeeper.delete(path, -1); //刪除節點 zooKeeper.close(); //關閉實例 } catch (Exception e) { e.printStackTrace(); }
ZooKeeper經過Auth和ACL完成節點的權限控制。
express
Auth表示某種認證,因爲一個ZooKeeper集羣可能被多個項目使用,各個項目 屬於不一樣的項目組,他們在進行開發時確定不想其餘項目訪問與本身相關的節點,這時能夠經過爲每一個項目組分配一個Auth,而後每一個項目組先經過Auth認 證之後再繼續相關的操做,這樣甲Auth認證的用戶就不能操做其餘Auth認證後建立的節點,從而實現各個項目之間的隔離。ZooKeeper提供了以下 方法完成認證,以下所示:
Void addAuthInfo(String scheme, byte[] auth) ,使用示例以下:
apache
@Test public void testFirstStep() { try { zk = new ZooKeeper(hostport, 1000000, null); String auth_type = "digest"; String auth = "joey:some"; String p = "/acl_digest"; zk.addAuthInfo(auth_type, auth.getBytes()); zk.create(p, "hello".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); Stat stat = new Stat(); System.out.println(new String(zk.getData(p, false, stat))); zk.close(); } catch(Exception ex) { ex.printStackTrace(); } } @Test public void testSecondStep() { String p = "/acl_digest"; try { zk = new ZooKeeper(hostport, 1000000, null); String authType = "digest"; String badAuth = "joey:someBAD"; zk.addAuthInfo(authType, badAuth.getBytes()); Stat stat = new Stat(); System.out.println(new String(zk.getData(p, false, stat))); } catch(Exception ex) { ex.printStackTrace(); //拋出異常 } finally { try { zk.delete(p, -1); zk.close(); } catch (Exception e) { e.printStackTrace(); } } }
ACL用於控制Znode的訪問,和Unix文件訪問權限相似,提供對某類用戶設置某種權限的能力(如Unix中對Owner提供讀、寫、執行的權限), 可是在ZooKeeper中沒有Owner、Group等概念,因而在ZooKeeper中使用ID表示某一類用戶,能夠對ID設置某種權限。 (ZooKeeper對ID的數量沒有限制,不像Unix文件僅支持三種類型用戶)
編程
ZooKeeper支持的權限:
CREATE: you can create a child node
READ: you can get data from a node and list its children.
WRITE: you can set data for a node
DELETE: you can delete a child node
ADMIN: you can set permissions
ZooKeeper內建的sheme:(scheme是ID的其中一個屬性)
world has a single id, anyone, that represents anyone.
auth doesn't use any id, represents any authenticated user.
digest uses a username:password string to generate MD5 hash which is then used as an ACL ID identity. Authentication is done by sending theusername:password in clear text. When used in the ACL the expression will be the username:base64 encoded SHA1 password digest.
ip uses the client host IP as an ACL ID identity. The ACL expression is of the form addr/bits where the most significant bits of addr are matched against the most significant bits of the client host IP.
ZK內建的ID:
ANYONE_ID_UNSAFE //任意用戶
AUTH_IDS //經過Auth認證過的用戶
內建的權限控制集合:
OPEN_ACL_UNSAFE: 建立任何人均可以操做的節點
READ_ACL_UNSAFE: 建立任何人均可以讀的節點
CREATOR_ALL_ACL: 設置了Auth的用戶可使用該ACL集合建立節點,該節點也只能被一樣Auth受權的用戶操做
數據結構
示例代碼以下:架構
@Test public void testACL_with_ip_scheme() { try { Id id = new Id(); id.setScheme("ip"); id.setId(InetAddress.getLocalHost().getHostAddress()); ACL acl = new ACL(); acl.setId(id); //對ID所指定的目標設置權限 acl.setPerms(Perms.ALL); List<ACL> acls = new ArrayList<ACL>(); acls.add(acl); //能夠添加多個運行的IP地址 String p = "/ip"; zk.create(p, p.getBytes(), acls, CreateMode.PERSISTENT); zk.delete(p, -1); //僅IP相同的用戶能夠對該進行進行操做 } catch(Exception ex) { ex.printStackTrace(); } }
Watcher
能夠設置Watcher的方式:異步
1) 在ZooKeeper的構造函數中能夠設置Watcher分佈式
2) 使用ZooKeeper.register(Watcher)顯示的更改在構造函數中設置的默認Watcher
3) 經過某些方法的調用能夠更改某個path對應節點的Watcher
具體能夠設置Watcher的方法以下所示:
1) 構造函數: state changes or node events
2) Register: 修改構造函數中指定的默認Watcher.
3) getData: triggered by sets data on the node, or deletes the node.
4) getChildren: triggered by deletes the node or creates/delete a child under the node.
5) exists: triggered by creates/delete the node or sets the data on the node.
其中構造函數階段指定的Watcher一直有效(register方式屬於該類),其他方法設置的Watcher僅有效一次。在方法調用時,若是指 定開啓watcher,若是該節點經過getData、getChildren和exists設置了Watcher,就觸發該Watcher,而後使得該 Watcher失效(但默認的Watcher還一直生效),不然觸發構造函數中設定的默認Watcher。
示例代碼以下:
class ExistsWatcher implements Watcher { @Override public void process(WatchedEvent event) { System.out.println("---------------------------"); System.out.println("setting by exist watcher"); System.out.println("path is : " + event.getPath()); System.out.println("type is : " + event.getType()); System.out.println("state is : " + event.getState()); System.out.println("---------------------------"); } } class DefaultWatcher implements Watcher { @Override public void process(WatchedEvent event) { System.out.println("=====>Default Watch Event: " + event.getType()); } } @Test public void testWatcher() { try { DefaultWatcher defaultWatcher = new DefaultWatcher(); ExistsWatcher existsWatcher = new ExistsWatcher(); String p = "/watcher"; ZooKeeper zk = new ZooKeeper(hostport, 300000, null); zk.register(defaultWatcher); Stat stat = zk.exists(p, existsWatcher); zk.create(p, p.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); stat = zk.exists(p, true); byte[] b = zk.getData(p, true, stat); System.out.println(new String(b)); stat = zk.exists(p, true); zk.setData(p, "Iloveyou".getBytes(), stat.getVersion()); stat = zk.exists(p, existsWatcher); zk.delete(p, stat.getVersion()); zk.close(); } catch(Exception ex) { ex.printStackTrace(); } }
運行結果以下:
=====>Default Watch Event: None --------------------------- setting by exist watcher path is : /watcher type is : NodeCreated state is : SyncConnected --------------------------- /watcher =====>Default Watch Event: NodeDataChanged --------------------------- setting by exist watcher path is : /watcher type is : NodeDeleted state is : SyncConnected ---------------------------
異步調用
顧名思義,異步調用是指在調用某個方法後不等待其返回,而是接着處理下面的任務,當方法調用完成時觸發某個回調函數,回調函數須要在方法調用時指定,而後在回調函數中處理方法調用的結果。
在ZK中,幾乎爲每一個方法都提供了異步調用的版本,如getData方法,其函數原型以下所示:
void getData(String path, boolean watch, DataCallback cb, Object ctx);
其中:
DataCallback爲提供回調函數的類,
ctx爲回調函數須要的參數
示例代碼以下:
Children2Callback cb = new Children2Callback() { @Override public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) { for (String s : children) { System.out.println("----->" + s); } System.out.println(ctx); //輸出:helloworld } }; zk.getChildren(path, true, cb, "helloworld");