ZooKeeper Java 例子
一个简单的观察者客户端
为了向你介绍Java API, 我们开发了一个非常简单的观察者客户端. 这个Zookeeper客户端观察一个Zookeeper节点的变化,并且通过开始和结束应用程序来响应.
必要条件
客户端有4个要求:
它作为参数:
Zookeeper服务的地址
znode的名称 - 要观察的那个节点
具有参数的可执行文件.
它获取与znode关联的数据,启动可执行文件.
如果znode改变, 客户端重新获取内容并重启执行文件.
如果znode文件消失,则客户端杀死执行文件.
程序定义
通常, ZooKeeper应用分为两个单元,一个维持连接,一个监控数据. 在应用程序中,名为Executor的类维持Zookeeper连接,名为DataMonitor 的类监控Zookeeper树的数据变化. 此外, Executor也包含主线程和包含执行逻辑. 它负责与用户进行交互,同时也负责通过一个参数与可执行程序性交互,根据订单状态去关闭和重启应用.
The Executor Class
Executor对象是样例容器的主要容器. 包含ZooKeeper 对象, DataMonitor, 如下所述的程序设计Program Design.
//来自Executor类... public static void main(String[] args) { if (args.length < 4) { System.err .println("USAGE: Executor hostPort znode filename program [args ...]"); System.exit(2); } String hostPort = args[0]; String znode = args[1]; String filename = args[2]; String exec[] = new String[args.length - 3]; System.arraycopy(args, 3, exec, 0, exec.length); try { new Executor(hostPort, znode, filename, exec).run(); } catch (Exception e) { e.printStackTrace(); } } public Executor(String hostPort, String znode, String filename, String exec[]) throws KeeperException, IOException { this.filename = filename; this.exec = exec; zk = new ZooKeeper(hostPort, 3000, this); dm = new DataMonitor(zk, znode, null, this); } public void run() { try { synchronized (this) { while (!dm.dead) { wait(); } } } catch (InterruptedException e) { } }
回想一下,Executor的工作是启动和停止可执行文件,也就是命令行传入的那个名称. 它这样做是为了响应Zookeeper对象触发的事件. 正如你上面看到代码, 执行器在Zookeeper类构造函数中传入一个自身作为Watcher类型参数. 它还将自身作为DataMonitorListener参数传入DataMonitor构造函数. 每一个Executor的定义,都实现了这些接口:
public class Executor implements Watcher, Runnable, DataMonitor.DataMonitorListener { ...
Watcher 接口是在Zookeeper Java API中定义. ZooKeeper使用它和持有它的容器通讯. 它仅有一个方法,process(), Zookeeper使用它来交互一些主线程感兴趣的事件, 例如Zookeeper的连接状态或者ZooKeeper会话. 在这个例子中,Executor只是简单的转发一些事件去DataMonitor中,以确定它们去做什么. 它只为了说明一点,按照惯例,Executor或者看起来像是Executor的类拥有Zookeeper连接对象,但是它会委派事件给其他的类. 它还将此作为监视事件的默认通道 (稍后详细叙述)
public void process(WatchedEvent event) { dm.process(event); }
DataMonitorListener 接口, 另一方面,它不属于Zookeeper API部分. 它是自定义接口, 为了这个样例程序而设计. DataMonitor使用它和它的拥有容器通讯,也就是Executor对象. DataMonitorListener看起来像这样:
public interface DataMonitorListener { /** * 节点的存在状态已经改变. */ void exists(byte data[]); /** * ZooKeeper会话不在有效. * * @param rc * ZooKeeper原因码 */ void closing(int rc); }
这个接口在DataMonitor类中定义和在Executor中实现. 当Executor.exists()被执行,Executor会根据需求启动和停止. 回想一下必要条件,当znode节点不存在时,需要杀死可执行文件.
当Executor.closing()被调用, Executor会决定自己是否关闭自身,来响应Zookeeper连接的永久消失.
你可能已经猜到了, DataMonitor是一个调用这些方法的对象,来响应Zookeeper状态的改变.
这儿有Executor的DataMonitorListener.exists() 和 DataMonitorListener.closing实现:
public void exists( byte[] data ) { if (data == null) { if (child != null) { System.out.println("Killing process"); child.destroy(); try { child.waitFor(); } catch (InterruptedException e) { } } child = null; } else { if (child != null) { System.out.println("Stopping child"); child.destroy(); try { child.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } } try { FileOutputStream fos = new FileOutputStream(filename); fos.write(data); fos.close(); } catch (IOException e) { e.printStackTrace(); } try { System.out.println("Starting child"); child = Runtime.getRuntime().exec(exec); new StreamWriter(child.getInputStream(), System.out); new StreamWriter(child.getErrorStream(), System.err); } catch (IOException e) { e.printStackTrace(); } } } public void closing(int rc) { synchronized (this) { notifyAll(); } }
The DataMonitor Class
DataMonitor类是Zookeeper逻辑的类. 它主要是异步和事件驱动的. DataMonitor将在构造函数中做的事情:
public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher, DataMonitorListener listener) { this.zk = zk; this.znode = znode; this.chainedWatcher = chainedWatcher; this.listener = listener; //检查这个节点是否存在开始工作,我们将要完成事件驱动 zk.exists(znode, true, this, null); }
调用ZooKeeper.exists()来检查znode是存在的,设置一个监听者,并将本身作为回调对象. 某种意义上来讲,它只是将事情启动,因为当监听者被触发时才真正处理开始.
不要将完成回调和监听回调混淆. ZooKeeper.exists()是完成回调,它发生在DataMonitor对象的StatCallback.processResult()方法中实现, 当服务中监听者操作完成异步设置,它会被调用.
监听者触发,另一方面,发送一个事件去Executor对象,因为Executor注册了一个Zookeeper对象的监听者.
另外,你可能也注意到DataMonitor也将自身注册为了一个更详细的事件监听者. 这是新的Zookeeper3.0.0提供的特性(支持多观察者). 但是,在这个例子中,DataMonitor并没有注册为一个观察者.
当ZooKeeper.exists()操作在服务器上完成时,在客户端ZooKeeper API会调用这个方法完成回调.
public void processResult(int rc, String path, Object ctx, Stat stat) { boolean exists; switch (rc) { case Code.Ok: exists = true; break; case Code.NoNode: exists = false; break; case Code.SessionExpired: case Code.NoAuth: dead = true; listener.closing(rc); return; default: // Retry errors zk.exists(znode, true, this, null); return; } byte b[] = null; if (exists) { try { b = zk.getData(znode, false, null); } catch (KeeperException e) { // We don't need to worry about recovering now. The watch callbacks will kick off any exception handling e.printStackTrace(); } catch (InterruptedException e) { return; } } if ((b == null && b != prevData) || (b != null && !Arrays.equals(prevData, b))) { listener.exists(b); prevData = b; } }
代码首先检查了关于节点存在的错误代码、致命错误、可恢复错误. 如果文件(或者znode)存在,如果znode存在它会获取数据,如果状态改变会接着调用Executor的exitsts回调. 注意,不需要对getData做任何异常处理,因为它会监听等待任何可能导致错误的异常: 如果在调用Zookeeper.getData()方法前节点被删除,由Zookeeper.exists()设置的监听事件触发一个回调;如果有一个通讯错误,当连接恢复时,连接事件将会触发.
最后,注意DataMonitor怎样处理监听事件:
public void process(WatchedEvent event) { String path = event.getPath(); if (event.getType() == Event.EventType.None) { // 我们被告知,连接状态已经改变 switch (event.getState()) { case SyncConnected: // 在这个特殊的例子中,我们不需要做任何事情- 监听者会自动重新注册的服务器和任何监听者触发客户端断开时都会交付。 break; case Expired: // 终止一切 dead = true; listener.closing(KeeperException.Code.SessionExpired); break; } } else { if (path != null && path.equals(znode)) { //在这个节点中有些事情发生了改变,让我们找到答案 zk.exists(znode, true, this, null); } } if (chainedWatcher != null) { chainedWatcher.process(event); } }
如果在会话失效以前客户端zookeeper库能够和ZooKeeper建立连接通道。则所有的会话监控器会与服务自动重新建立(自动重置监控器是zookeeper3.0.0里的一个新特性). 想了解更多请查看ZooKeeper Watche. 在这方法中,当DataMonitor获得一个znode的事件时,它会调用calls ZooKeeper.exists()去发现改变了什么.
Complete Source Listings
/** * 一个简单的例子,基于znode使用DataMonitor去开始和停止一个可执行文件.
* 这个程序监听指定的znode,保存到相应的文件系统znode节点.
* 当节点存在会启动带参数的指定程序,当znode接点被删除时会关闭该程序. */ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; public class Executor implements Watcher, Runnable, DataMonitor.DataMonitorListener { String znode; DataMonitor dm; ZooKeeper zk; String filename; String exec[]; Process child; public Executor(String hostPort, String znode, String filename, String exec[]) throws KeeperException, IOException { this.filename = filename; this.exec = exec; zk = new ZooKeeper(hostPort, 3000, this); dm = new DataMonitor(zk, znode, null, this); } /** * @param args */ public static void main(String[] args) { if (args.length < 4) { System.err .println("USAGE: Executor hostPort znode filename program [args ...]"); System.exit(2); } String hostPort = args[0]; String znode = args[1]; String filename = args[2]; String exec[] = new String[args.length - 3]; System.arraycopy(args, 3, exec, 0, exec.length); try { new Executor(hostPort, znode, filename, exec).run(); } catch (Exception e) { e.printStackTrace(); } } /*************************************************************************** * 我们不需要处理任何事件,我们只需要将请求转发给dm. * * @see org.apache.zookeeper.Watcher#process(org.apache.zookeeper.proto.WatcherEvent) */ public void process(WatchedEvent event) { dm.process(event); } public void run() { try { synchronized (this) { while (!dm.dead) { wait(); } } } catch (InterruptedException e) { } } public void closing(int rc) { synchronized (this) { notifyAll(); } } static class StreamWriter extends Thread { OutputStream os; InputStream is; StreamWriter(InputStream is, OutputStream os) { this.is = is; this.os = os; start(); } public void run() { byte b[] = new byte[80]; int rc; try { while ((rc = is.read(b)) > 0) { os.write(b, 0, rc); } } catch (IOException e) { } } } public void exists(byte[] data) { if (data == null) { if (child != null) { System.out.println("Killing process"); child.destroy(); try { child.waitFor(); } catch (InterruptedException e) { } } child = null; } else { if (child != null) { System.out.println("Stopping child"); child.destroy(); try { child.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } } try { FileOutputStream fos = new FileOutputStream(filename); fos.write(data); fos.close(); } catch (IOException e) { e.printStackTrace(); } try { System.out.println("Starting child"); child = Runtime.getRuntime().exec(exec); new StreamWriter(child.getInputStream(), System.out); new StreamWriter(child.getErrorStream(), System.err); } catch (IOException e) { e.printStackTrace(); } } } }
/** * 一个简单的类监控数据和Zookeeper节点. 它使用异步Zookeeper API. */ import java.util.Arrays; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.AsyncCallback.StatCallback; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.data.Stat; public class DataMonitor implements Watcher, StatCallback { ZooKeeper zk; String znode; Watcher chainedWatcher; boolean dead; DataMonitorListener listener; byte prevData[]; public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher, DataMonitorListener listener) { this.zk = zk; this.znode = znode; this.chainedWatcher = chainedWatcher; this.listener = listener; // 如果这个节点存在检查并开始程序. 我们完全由事件驱动. zk.exists(znode, true, this, null); } /** * 其他类通过实现这些方法来使用DataMonitor */ public interface DataMonitorListener { /** * 节点的存在状态已经改变. */ void exists(byte data[]); /** * ZooKeeper会话不会再生效. * * @param rc * Zookeeper原因代码 */ void closing(int rc); } public void process(WatchedEvent event) { String path = event.getPath(); if (event.getType() == Event.EventType.None) { // We are are being told that the state of the connection has changed switch (event.getState()) { case SyncConnected: // 在这个例子中,我们不需要做任何处理.
// 监听者会自动重新注册和任何触发的客户端都会重连. break; case Expired: // It's all over dead = true; listener.closing(KeeperException.Code.SessionExpired); break; } } else { if (path != null && path.equals(znode)) { // 在这个节点发生了改变,让我们来看看 zk.exists(znode, true, this, null); } } if (chainedWatcher != null) { chainedWatcher.process(event); } } public void processResult(int rc, String path, Object ctx, Stat stat) { boolean exists; switch (rc) { case Code.Ok: exists = true; break; case Code.NoNode: exists = false; break; case Code.SessionExpired: case Code.NoAuth: dead = true; listener.closing(rc); return; default: // Retry errors zk.exists(znode, true, this, null); return; } byte b[] = null; if (exists) { try { b = zk.getData(znode, false, null); } catch (KeeperException e) { // 现在我们不需要担心关于恢复了.
// 监听者回调将会对任何异常进行处理. e.printStackTrace(); } catch (InterruptedException e) { return; } } if ((b == null && b != prevData) || (b != null && !Arrays.equals(prevData, b))) { listener.exists(b); prevData = b; } } }