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");