zookeeper入门案例

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 test
Created /test

响应

Stopping child
Starting child
test
  • 修改节点数据
[zk: localhost:2181(CONNECTED) 2] set /test test1
cZxid = 0x200000005
ctime = Tue Nov 21 15:17:49 CST 2017
mZxid = 0x200000008
mtime = Tue Nov 21 15:19:50 CST 2017
pZxid = 0x200000005
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

响应

Stopping child
Starting child
test1
4、参考

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值