zookeeper入门案例

来源:互联网 发布:学生水杯推荐知乎 编辑:程序博客网 时间:2024/05/16 12:00

1、案例描述

这是一个从从zookeeper官网获取的简单案例。请先确保你已经安装好一个zookeeper机器环境,如果没有请参考我的文章《zookeeper入门》http://blog.csdn.net/koflance/article/details/78586235。

案例基本设计要求是,监听一个zookeeper的节点路径,比如/test,如果节点路径上保存的数据发生了变更,则将数据写入到指定的本地文件中,同时启动一个本地脚本命令,比如用cat将该本地文件读取出来,显示在终端。

基本步骤如下:

  • 实例化一个zookeeper客户端,并将一个哨兵(watcher)注册到该节点下;
  • 实例化一个节点数据监听器(datamointor),用异步方式(zk.exists)获取节点数据的变更状态;
  • 实例化一个脚本执行器(Executor),并将其注册为数据监听器(datamointor)的观察者(listener);
  • 数据监听器(datamointor)收到zk客户端的变更消息,立即用异步方式(zk.exists)获取节点数据,并判断是否变更(b != preData),如果变更,则通知观察者(listener);
  • 脚本执行器收到datamointor的数据变更通知,立即将获取到的节点数据写入文件(filename),写入之后启用本地脚本任务process,将内容打印出来。

2、案例代码

  • Executor 脚本执行器及zookeeper客户端和哨兵
package com.xxxx.xxxx.zookeeper;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooKeeper;import java.io.*;/** * 在指定的znode路径节点上,使用DataMonitor获取节点数据或状态变更情况。 * 这个类会观察指定znode节点并保存数据在该路径上,当znode存在时,启动指定的程序;当znode节点不存在时,关闭指定的程序。 * * Created by wushiweijun on 2017/11/20. */public class Executor implements Watcher, Runnable, DataMonitor.DataMonitorListener {    private final DataMonitor dm;    private final ZooKeeper zk;    private final String filename;    private final String[] exec;    /*需要执行命令的程序*/    private Process child;    public static void main(String[] args) {        args = "localhost:2181,localhost:2182,localhost:2183 /test /Users/wushiweijun/Documents/test/zookeeper/executor.txt cat /Users/wushiweijun/Documents/test/zookeeper/executor.txt".split(" ");//        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();        }    }    /**     *     * @param hostPort 集群的host地址列表,多个用逗号分隔,例如host:port,host:port,host:port/app/a     * @param znode  访问的节点路径     * @param filename     * @param exec 执行的命令程序,例如create xxx     * @throws KeeperException     * @throws IOException     */    public Executor(String hostPort, String znode, String filename,                    String exec[]) throws KeeperException, IOException {        this.filename = filename;        this.exec = exec;        /*        * 创建ZK客户端,但要注意,该对象实例化时,并不会进行连接服务端,而只是初始化连接,真正的连接是异步。        * zookeeper会随机挑选(不是按照顺序)一个hostPort进行尝试,直到找到一个可以连接成功的host或者sessionTimeout        * 入参格式如下:        * hostport:        *   host:port,host:port,host:port -- 针对集群        *   host:port,host:port,host:port/app/a --针对需要初始默认的根目录情况,这个被称为chroot suffix        * sessionTimeout:        *   链接超时时间,单位毫秒        * watcher:        *   一个哨兵回调对象,用于监听节点状态的变更        */        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) {        }    }    /***************************************************************************     * WatchedEvent可以告诉你三个信息:     *  1、发生了什么     *  2、在那个znode路径发生的     *  3、节点当前状态是什么     * @see org.apache.zookeeper.Watcher#process(WatchedEvent)     */    @Override    public void process(WatchedEvent event) {        /*znode路径下节点状态变更*/        dm.process(event);    }    /**     * 节点数据发生变更     * @param data     */    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);                println(child.getInputStream(), System.out);                println(child.getErrorStream(), System.err);            } catch (IOException e) {                e.printStackTrace();            }        }    }    private void println(InputStream inputStream, PrintStream printStream) {        new Thread(new Runnable() {            @Override            public void run() {                InputStreamReader inputStreamReader = null;                try {                    inputStreamReader = new InputStreamReader(inputStream);                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);                    String line = null;                    while ((line = bufferedReader.readLine()) != null) {                        printStream.println(line);                    }                } catch (Exception e) {                    ;                } finally {                    if (inputStreamReader != null) {                        try {                            inputStreamReader.close();                        } catch (Exception e) {                            ;                        }                    }                }            }        }).start();    }    /**     * 节点无权、过期、不存在等情况     * @param rc the ZooKeeper reason code     */    public void closing(int rc) {        synchronized (this) {            notifyAll();        }    }}
  • DataMonitor 节点数据变更监视器
package com.xxxx.xxxx.zookeeper;import org.apache.zookeeper.*;import org.apache.zookeeper.data.Stat;import java.util.Arrays;import static org.apache.zookeeper.KeeperException.Code;/** * 采用异步回调方式(zk.exists)判断获取节点的数据,并进行判断是否和之前的一致, * 不一致说明变更过,会触发listener.exist, * 如果发现节点不存在了或者节点无权限或sessiontimeout,则触发listener.close * * Created by wushiweijun on 2017/11/20. */public class DataMonitor implements Watcher, AsyncCallback.StatCallback {    private ZooKeeper zk;    private String znode;    private DataMonitorListener listener;    private Watcher chainedWatcher = null;    public boolean dead;    private byte[] prevData;    public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher,                       DataMonitorListener listener) {        this.zk = zk;        this.znode = znode;        this.chainedWatcher = chainedWatcher;        this.listener = listener;        // 异步回调方式,判断节点是否存在,如果存在则获取数据,        // 如果对比之前数据发现不一致,则触发listener        zk.exists(znode, true, this, null);    }    /**     * 判断节点状态的异步回调函数     *     * @param rc 返回节点状态     * @param path 节点路径     * @param ctx 上下文,即zk.exists传入的ctx参数     * @param stat 节点当前状态元数据     */    @Override    public void processResult(int rc, String path, Object ctx, Stat stat) {        boolean exists;        switch (Code.get(rc)) {            case OK:                exists = true;                break;            case NONODE:                exists = false;                break;            case SESSIONEXPIRED:            case NOAUTH:                dead = true;                /*没有权限访问或者session过期,即没有ACL权限,直接关闭*/                listener.closing(rc);                return;            default:                // 重新监听,直至有效,这样就迭代循环了                // 异步回调方式,判断节点是否存在,如果存在则获取数据,                // 如果对比之前数据发现不一致,则触发listener                zk.exists(znode, true, this, null);                return;        }        byte b[] = null;        if (exists) {            try {                /*                * 如果存在, 则获取指定znode路径的数据,                * 如果watch=true,则会在获取数据的同时,放一个哨兵到znode,下次有变更,则会触发                */                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;        }    }    @Override    public void process(WatchedEvent event) {        String path = event.getPath();        /*判断时间类型*/        if (event.getType() == Event.EventType.None) {            // 链接状态发生改变            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:                    // 链接失效了                    dead = true;                    listener.closing(Code.SESSIONEXPIRED.intValue());                    break;            }        } else {            if (path != null && path.equals(znode)) {                // 异步回调方式,判断节点是否存在,如果存在则获取数据,                // 如果对比之前数据发现不一致,则触发listener                zk.exists(znode, true, this, null);            }        }        if (chainedWatcher != null) {            chainedWatcher.process(event);        }    }    public interface DataMonitorListener {        /**         * 节点数据发生变更         */        void exists(byte data[]);        /**         * zookeeper的会话过期或者没有节点访问权限         *         * @param rc the ZooKeeper reason code         */        void closing(int rc);    }}

3、案例测试

  • 启动Executor
  • 用终端启动一个zk客户端
zkCli.sh -server localhost:2181
  • 创建节点
[zk: localhost:2181(CONNECTED) 1] create /test testCreated /test

响应

Stopping childStarting childtest
  • 修改节点数据
[zk: localhost:2181(CONNECTED) 2] set /test test1cZxid = 0x200000005ctime = Tue Nov 21 15:17:49 CST 2017mZxid = 0x200000008mtime = Tue Nov 21 15:19:50 CST 2017pZxid = 0x200000005cversion = 0dataVersion = 1aclVersion = 0ephemeralOwner = 0x0dataLength = 5numChildren = 0

响应

Stopping childStarting childtest1

4、参考

[1] http://zookeeper.apache.org/doc/trunk/javaExample.html

原创粉丝点击