A Simple Watch Client

为了向你介绍ZooKeeper Java API,我们开发了一个非常简单的监视器客户端。ZooKeeper客户端监视一个ZooKeeper节点的改变并且通过开始和停止一个程序来作出响应。

必备条件

客户端有四个必备条件:

  • 它作为参数:
    1. ZooKeeper服务端的地址
    2. znode的名字 - 被监视的节点
    3. 写输出内容的文件名字
    4. 带有参数的可执行文件
  • 它抓取这个znode的数据并且开始这个可执行文件
  • 如果znode改变,客户端重新抓取内容并且重启这个可执行文件
  • 如果znode消失,客户端杀死这个可执行文件

程序设计

按照惯例,ZooKeeper应用被分为两部分,一部分维护连接,另一部分监视数据。在这个应用中,Executor的类维护ZooKeeper连接,DataMonitor的类监视ZooKeeper树中的数据。同时,Executor包含主线程和执行逻辑,它负责很少的用户交互,和你作为参数传进去的可执行程序的交互,还有那一个例子关闭和重启,根据znode的状态。

Executor 类

Executor对象是这个例子程序的主要容器。它包含ZooKeeper对象,DataMonitor,就像上面程序设计描述的那样。

   // from the Executor class...

    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对象触发的事件。就像你在上面的代码看到的那样,Executor传入一个引入给他自己作为ZooKeeper构造函数的Watcher 参数。

它也传入一个引用给它自己作为DataMonitor的构造器的DataMonitorListener参数。每一个的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 {
/**
* The existence status of the node has changed.
*/
void exists(byte data[]); /**
* The ZooKeeper session is no longer valid.
*
* @param rc
* the ZooKeeper reason code
*/
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();
}
}

DataMonitor 类

The DataMonitor class has the meat of the ZooKeeper logic(这句咋翻译)。它几乎上是异步和事件驱动。 DataMonitor kicks things off in the constructor with:

public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher,
DataMonitorListener listener) {
this.zk = zk;
this.znode = znode;
this.chainedWatcher = chainedWatcher;
this.listener = listener; // Get things started by checking if the node exists. We are going
// to be completely event driven
zk.exists(znode, true, this, null);
}

调用ZooKeeper.exists()来检查znode是否存在,设置一个监视器,并且传递它自己的引用给它自己作为完成回调的对象,在这个意义上,it kicks things off,因为真实的处理在监视器被触发的时候发生。

注意

不要把完成回调和监视回调弄混淆了。ZooKeeper.exists()完成回调,它发生在DataMonitor对象的StatCallback.processResult()方法实现中,当异步的监视器set操作(被ZooKeeper.exists())在服务端完成的时候被调用。

另一方面,监视器的触发,发送一个事件给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)存在,它从znode获取数据,并且如果状态已经改变它调用Executor的exists()回调函数。注意,它不需要为getData调用做任何Exception处理因为它有任何可能导致错误的监视器:如果在ZooKeeper.getData()方法之前这个节点被删除,通过ZooKeeper.exists()设置的监听事件会触发一个回调;如果有通信错误,当连接回来的时候会触发一个连接监听事件。

最后,注意DataMonitor是怎么处理监听事件的:

 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:
// In this particular example we don't need to do anything
// here - watches are automatically re-registered with
// server and any watches triggered while the client was
// disconnected will be delivered (in order of course)
break;
case Expired:
// It's all over
dead = true;
listener.closing(KeeperException.Code.SessionExpired);
break;
}
} else {
if (path != null && path.equals(znode)) {
// Something has changed on the node, let's find out
zk.exists(znode, true, this, null);
}
}
if (chainedWatcher != null) {
chainedWatcher.process(event);
}
}

如果客户端ZooKeeper库在会话到期(到期事件)之前可以和ZooKeeper重新建立通信通道(同步连接事件)所有会话的监视器将自动地在服务端被建立(自动重置监视器是ZooKeeper 3.0.0的新特性)。关于这点的更多信息可以参考ZooKeeper Watches。

再深入这个方法一些,当DataMonitor等到一个znode的事件,它调用ZooKeeper.exists()来查找什么被改变了。

完整的源码列表

Executor.java

/**
* A simple example program to use DataMonitor to start and
* stop executables based on a znode. The program watches the
* specified znode and saves the data that corresponds to the
* znode in the filesystem. It also starts the specified program
* with the specified arguments when the znode exists and kills
* the program if the znode goes away.
*/
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();
}
} /***************************************************************************
* We do process any events ourselves, we just need to forward them on.
*
* @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();
}
}
}
}

DataMonitor.java

/**
* A simple class that monitors the data and existence of a ZooKeeper
* node. It uses asynchronous ZooKeeper APIs.
*/
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;
// Get things started by checking if the node exists. We are going
// to be completely event driven
zk.exists(znode, true, this, null);
} /**
* Other classes use the DataMonitor by implementing this method
*/
public interface DataMonitorListener {
/**
* The existence status of the node has changed.
*/
void exists(byte data[]); /**
* The ZooKeeper session is no longer valid.
*
* @param rc
* the ZooKeeper reason code
*/
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:
// In this particular example we don't need to do anything
// here - watches are automatically re-registered with
// server and any watches triggered while the client was
// disconnected will be delivered (in order of course)
break;
case Expired:
// It's all over
dead = true;
listener.closing(KeeperException.Code.SessionExpired);
break;
}
} else {
if (path != null && path.equals(znode)) {
// Something has changed on the node, let's find out
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) {
// 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;
}
}
}

ZooKeeper Java例子(六)的更多相关文章

  1. ZooKeeper java例子解读

    转载链接:https://blog.csdn.net/liyiming2017/article/details/83276706 需求理解我们先回顾一下例子的需求,此客户端有如下四个需求: 1.它接收 ...

  2. ZooKeeper学习第六期---ZooKeeper机制架构

    一.ZooKeeper权限管理机制 1.1 权限管理ACL(Access Control List) ZooKeeper 的权限管理亦即ACL 控制功能,使用ACL来对Znode进行访问控制.ACL的 ...

  3. ZooKeeper学习第六期---ZooKeeper机制架构(转)

    转载来源:https://www.cnblogs.com/sunddenly/p/4133784.html 一.ZooKeeper权限管理机制 1.1 权限管理ACL(Access Control L ...

  4. 9. 使用ZooKeeper Java API编程

    ZooKeeper是用Java开发的,3.4.6版本的Java API文档可以在http://zookeeper.apache.org/doc/r3.4.6/api/index.html上找到. Ti ...

  5. 20165210 Java第六周学习总结

    20165210 Java第六周学习总结 教材学习内容 第八章学习总结 String类: 构造String对象: 1. 常量对象 2. String对象 3. 引用String常量 字符串的并置: S ...

  6. Zookeeper java api

     Zookeeper java api 主要有以下几个: 方法名称 描述 String create(final String path, byte data[], List acl, CreateM ...

  7. “全栈2019”Java第六十九章:内部类访问外部类成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. “全栈2019”Java第六十八章:外部类访问内部类成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. “全栈2019”Java第六十七章:内部类、嵌套类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

随机推荐

  1. LeetCode - 268. Missing Number - stable_sort应用实例 - ( C++ ) - 解题报告

    1.题目大意 Given an array nums, write a function to move all 0's to the end of it while maintaining the ...

  2. js中的数组对象排序

    一.普通数组排序 js中用方法sort()为数组排序.sort()方法有一个可选参数,是用来确定元素顺序的函数.如果这个参数被省略,那么数组中的元素将按照ASCII字符顺序进行排序.如: var ar ...

  3. Python中的list

    list的创建 1 字面量 >>>L = [1, 2, 3] [1, 2, 3] 2 通过iterable可迭代对象,比如str对象,range对象,map对象 >>&g ...

  4. Thunder团队第一次Scrum会议

    Scrum会议1 小组名称:Thunder 项目名称:待定 参会成员: 王航(Master):http://www.cnblogs.com/wangh013/ 李传康:http://www.cnblo ...

  5. servlet映射路径

    1 访问映射过程 问题:访问URL:http://localhost:8080/day10/first  ,服务器如何相应的? 前提: tomcat服务器启动时,首先加载webapps中的每个web应 ...

  6. Spring管理事务默认回滚的异常

    一.默认方式 Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类),Error进行回滚. 如果一个方法抛出Exception或者Checked异 ...

  7. LintCode-72.中序遍历和后序遍历树构造二叉树

    中序遍历和后序遍历树构造二叉树 根据中序遍历和后序遍历树构造二叉树 注意事项 你可以假设树中不存在相同数值的节点 样例 给出树的中序遍历: [1,2,3] 和后序遍历: [1,3,2] 返回如下的树: ...

  8. 代码编写规范Asp.Net(c#)

    1        目的 为了统一公司软件开发的设计过程中关于代码编写时的编写规范和具体开发工作时的编程规范,保证代码的一致性,便于交流和维护,特制定此规范. 2        范围 本规范适用于开发组 ...

  9. 第63天:json的两种声明方式

    一. json 两种声明方式 1. 对象声明   var  json = {width:100,height:100} 2. 数组声明   var  man = [        //  数组的 js ...

  10. BZOJ 2460 元素(贪心+线性基)

    显然线性基可以满足题目中给出的条件.关键是如何使得魔力最大. 贪心策略是按魔力排序,将编号依次加入线性基,一个数如果和之前的一些数异或和为0就跳过他. 因为如果要把这个数放进去,那就要把之前的某个数拿 ...