ZooKeeper笔记

简介:

Zookeeper是一个高可用的分布式管理与协调框架,基于Paxos算法(原子消息广播协议),能够很好地保证分布式环境中数据的一致性

顺序一致性:从客户端发起的事务请求,最终会严格地按照其发起的顺序被应用到Zookeeper中

原子性:所有事务请求的结果在集群环境中所有机器上的应用情况应该是一致的,也就是说要么集群中所有的机器都应用了这一事务,要么所有机器都没有应用这一事务.

单一视图:无论客户端连接的是哪一个Zookeeper服务器,其获取的服务端数据都是一致的

可靠性:一旦服务器成功地应用了某一事务,并完成了对客户端的响应,那么该事务引起的服务端状态将被一致地保留下来,除非有另一个事务对其进行更改.

树形结构!

Zookeeper服务分三种角色:leader,Follower,Observer,其中Follower与Observer又统称为Learner

Leader:负责客户端的writer请求类型

Follower:负责客户端的reader类型请求和leader选举等

Observer:特殊的Follower,可以接受客户端的reader请求,但不参与选举,不接受任何的同步写入请求,只负责与leader同步数据

典型应用场景:

配置管理

集群管理

发布订阅

数据库切换

分布式日志收集

分布式锁与队列管理

Zookeeper的安装:

  1. 结构:一共三个节点 (zk服务器集群规模不小于3个节点),要求服务器之间系统时间保持一致。
  2. 上传zk  进行解压: tar zookeeper-3.4.5.tar.gz 
  3. 重命名: mv zookeeper-3.4.5 zookeeper
  4. 修改环境变量: vi /etc/profile                   export ZOOKEEPER_HOME=/usr/local/zookeeper                  export PATH=.:$ZOOKEEPER_HOME/bin      刷新: source /etc/profile
  5. 到zookeeper下修改配置文件  cd /usr/local/zookeeper/conf                    mv zoo_sample.cfg zoo.cfg
  6. 修改conf:  vi zoo.cfg  修改两处  (1)dataDir=/usr/local/zookeeper/data (2)最后面添加 server.0=bhz:2888:3888   server.1=hadoop1:2888:3888   server.2=hadoop2:2888:3888
  7. 服务器标识配置:   创建文件夹:mkdir data         创建文件myid并填写内容为0,1,2:vi myid (内容为服务器标识:0,1,2)

启动zookeeper:

路径:/usr/local/zookeeper/bin

执行:zkServer.sh start (注意这里3台机器都要进行启动)

状态:zkServer.sh status(在三个节点上检验zk的mode,一个leader和俩个follower)

操作zookeeper (shell)

zkCli.sh 进入zookeeper客户端:

根据提示命令进行操作: 

查找:ls /   ls /zookeeper

创建并赋值:create /ym 1111

获取:get /ym 

设值:set /ym 2222

可以看到zookeeper集群的数据一致性

创建节点有俩种类型:短暂(ephemeral):仅当前连接有效,连接断开,则失效被删除 

          持久(persistent):永久有效,除非被删除

zoo.cfg详解

        tickTime: 基本事件单元,以毫秒为单位。这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime时间就会发送一个心跳。

        dataDir: 存储内存中数据库快照的位置,顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。

        clientPort: 这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

        initLimit: 这个配置项是用来配置 Zookeeper 接受客户端初始化连接时最长能忍受多少个心跳时间间隔数,当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。

        syncLimit: 这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒

        server.A = B:C:D : 

         A表示这个是第几号服务器,

         B 是这个服务器的 ip 地址;

         C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;

         D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader

Stat详解:

cZxid  数据节点创建时的事务ID
ctime  数据节点创建时的时间
mZxid  数据节点最后一次更新时的事务ID
mtime  数据节点最后一次更新时的时间
pZxid  数据节点的子节点列表最后一次被修改(是子节点列表变更,而不是子节点内容变更)时的事务ID
cversion  子节点的版本号
dataVersion  数据节点的版本号
aclVersion  数据节点的ACL版本号 
ephemeralOwner   如果节点是临时节点,则表示创建该节点的会话的SessionID;如果节点是持久节点,则该属性值为0
dataLength  数据内容的长度
numChildren  数据节点当前的子节点个数

 

 

 

 

 

 

 

 

Zooleeper原生API:

  1. JAVA想要实现对于Zookeeper操作,需要先新建Zookeeper对象,一般需要三个参数:zookeeper集群地址,session超时时间,watcher配置(需要重写process方法)监听
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.129:2181,192.168.238.131:2181,192.168.238.132:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 2000;//ms 
    /** 信号量,阻塞程序执行,用于等待zookeeper连接成功,发送成功信号 */
    static final CountDownLatch connectedSemaphore = new CountDownLatch(1);
    ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){
                @Override
                public void process(WatchedEvent event) {
                    //获取事件的状态
                    KeeperState keeperState = event.getState();
                    EventType eventType = event.getType();
                    //如果是建立连接
                    if(KeeperState.SyncConnected == keeperState){
                        if(EventType.None == eventType){
                            //如果建立连接成功,则发送信号量,让后续阻塞程序向下执行
                            connectedSemaphore.countDown();
                            System.out.println("zk 建立连接");
                        }
                    }
                }
            });
    
    //进行阻塞,只有连接zookeeper成功才继续执行主线程
    connectedSemaphore.await();

  2. 利用上文代码中的zk对象,创建,获取,修改,删除节点操作.
    创建:
    第一个参数为节点路径

    第二个参数为Byte[]类型的节点值
    第三个参数为Ids授权策略,OPEN_ACL_UNSAFE为开放状态,CREATOR_ALL_ACL为需要认证状态
    (此状态时创建节点前需要
    //添加节点授权
    zk.addAuthInfo("digest","123456".getBytes());其中"digest"为认证方式,"123456"为凭证
    认证方式有如下几种:
    IP:ip模式通过ip地址粒度进行权限控制模式,例如配置了:192.168.110.135即表示权限控制都是针对这个ip地址的,同时也支持按网段分配,比如:192.168.110.*
    Digest:digest是最常用的权限控制模式,也更符合我们对权限控制的认识,其类似于"username:password"形式的权限标识进行权限配置。ZK会对形成的权限标识先后进行两次编码处理,粉笔是SHA-1加密算法和Base64编码。
    World:World是一直最开放的权限控制模式。这种模式可以看做为特殊的Digest,他仅仅是一个标识而已。
    Super:超级用户模式,在超级用户模式下可以对ZK任意进行操作。
    )
    第四个参数为节点类型,PERSISTENT为持久节点,EPHEMERAL为短暂节点
    需要注意:zk.create方法不能递归创建节点.如想创建testRoot下面的a1节点,不能直接创建/testRoot/a1,需要先创建父节点,然后创建子节点,不然会报异常,异常显示节点不存在
                   zk.create的返回值为字符串,内容为创建节点的详细信息(创建时间戳,修改时间戳,修改的版本,初始化的版本,zxid等)
    //创建父节点
    //创建持久节点
    zk.create("/testRoot", "testRoot111".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    //创建临时节点
    zk.create("/testRoot1", "testRoot222".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    
    
    //创建子节点
    zk.create("/testRoot/a1", "1111".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    zk.create("/testRoot/a2", "2222".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

    获取:
    zk.getData方法:
    第一个参数为要获取的节点地址
    第二个参数为是否设置watcher监听
    第三个参数为stat节点状态信息
    返回值为byte[]
    zk.getChildren方法:
    第一个参数为要获取的节点地址
    第二个参数为是否设置watcher监听
    返回值为List,foreach遍历进行获取
    需要注意:若watcher设置为true,则会在获取的节点设置监听,当节点发生变化(增删改)时,会返回event事件,这样的watcher在经过一次后就会失效.
                   getChildren方法是不能递归获取子节点的,只可以获取当前节点的下一级子节点,孙子节点无法获取.

    //获取父节点
            byte[] data = zk.getData("/testRoot", false, null);
            System.out.println(new String(data));
            //获取子节点
            List<String> children = zk.getChildren("/testRoot", null);
            for (String p:children) {
                System.out.println(p);
                System.out.println(new String(zk.getData("/testRoot/"+p,false,null)));
            }            

    修改:
    setData方法:
    第一个参数为要修改的节点路径
    第二个参数为要修改的内容
    第三个参数为要修改的版本(-1为无视版本)
    返回值为Stat节点状态信息

    //修改节点的值
    Stat stat = zk.setData("/testRoot", "modify data root".getBytes(), -1);

    删除:
    zk.delete方法:
    第一个参数为要删除的节点路径
    第二个参数为要修改的版本(-1为无视版本)
    需要注意:zk.delete仍然是不可以递归删除的,若想删除的节点下面有子节点,则需要先删除子节点再删除父节点,否则报异常
    无返回值
    zk.exists方法:判断节点是否存在
    第一个参数为要删除的节点路径
    第二个参数为是否设置watcher监听
    返回值为Stat节点状态信息
    需要注意:若watcher设置为true,则会在获取的节点设置监听,当节点发生变化(增删改)时,会返回event事件,这样的watcher在经过一次后就会失效.

    //删除节点
    zk.delete("/testRoot/children", -1);
    //判断节点是否存在
    Stat stat = zk.exists("/testRoot", false);
  3. 自定义watcher实现监听

    public class ZooKeeperWatcher implements Watcher {
    
        /** 定义原子变量 */
        AtomicInteger seq = new AtomicInteger();
        /** 定义session失效时间 */
        private static final int SESSION_TIMEOUT = 10000;
        /** zookeeper服务器地址 */
        private static final String CONNECTION_ADDR = "192.168.1.121:2181,192.168.1.122:2181,192.168.1.123:2181";
        /** zk父路径设置 */
        private static final String PARENT_PATH = "/p";
        /** zk子路径设置 */
        private static final String CHILDREN_PATH = "/p/c";
        /** 进入标识 */
        private static final String LOG_PREFIX_OF_MAIN = "【Main】";
        /** zk变量 */
        private ZooKeeper zk = null;
        /**用于等待zookeeper连接建立之后 通知阻塞程序继续向下执行 */
        private CountDownLatch connectedSemaphore = new CountDownLatch(1);
    
        /**
         * 创建ZK连接
         * @param connectAddr ZK服务器地址列表
         * @param sessionTimeout Session超时时间
         */
        public void createConnection(String connectAddr, int sessionTimeout) {
            this.releaseConnection();
            try {
                //this表示把当前对象进行传递到其中去(也就是在主函数里实例化的new ZooKeeperWatcher()实例对象)
                zk = new ZooKeeper(connectAddr, sessionTimeout, this);
                System.out.println(LOG_PREFIX_OF_MAIN + "开始连接ZK服务器");
                connectedSemaphore.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 关闭ZK连接
         */
        public void releaseConnection() {
            if (this.zk != null) {
                try {
                    this.zk.close();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 创建节点
         * @param path 节点路径
         * @param data 数据内容
         * @return 
         */
        public boolean createPath(String path, String data, boolean needWatch) {
            try {
                //设置监控(由于zookeeper的监控都是一次性的所以 每次必须设置监控)
                this.zk.exists(path, needWatch);
                System.out.println(LOG_PREFIX_OF_MAIN + "节点创建成功, Path: " + 
                                   this.zk.create(    /**路径*/ 
                                                       path, 
                                                       /**数据*/
                                                       data.getBytes(), 
                                                       /**所有可见*/
                                                       Ids.OPEN_ACL_UNSAFE, 
                                                       /**永久存储*/
                                                       CreateMode.PERSISTENT ) +     
                                   ", content: " + data);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    
        /**
         * 读取指定节点数据内容
         * @param path 节点路径
         * @return
         */
        public String readData(String path, boolean needWatch) {
            try {
                System.out.println("读取数据操作...");
                return new String(this.zk.getData(path, needWatch, null));
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
    
        /**
         * 更新指定节点数据内容
         * @param path 节点路径
         * @param data 数据内容
         * @return
         */
        public boolean writeData(String path, String data) {
            try {
                System.out.println(LOG_PREFIX_OF_MAIN + "更新数据成功,path:" + path + ", stat: " +
                                    this.zk.setData(path, data.getBytes(), -1));
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    
        /**
         * 删除指定节点
         * 
         * @param path
         *            节点path
         */
        public void deleteNode(String path) {
            try {
                this.zk.delete(path, -1);
                System.out.println(LOG_PREFIX_OF_MAIN + "删除节点成功,path:" + path);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 判断指定节点是否存在
         * @param path 节点路径
         */
        public Stat exists(String path, boolean needWatch) {
            try {
                return this.zk.exists(path, needWatch);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 获取子节点
         * @param path 节点路径
         */
        private List<String> getChildren(String path, boolean needWatch) {
            try {
                System.out.println("读取子节点操作...");
                return this.zk.getChildren(path, needWatch);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 删除所有节点
         */
        public void deleteAllTestPath(boolean needWatch) {
            if(this.exists(CHILDREN_PATH, needWatch) != null){
                this.deleteNode(CHILDREN_PATH);
            }
            if(this.exists(PARENT_PATH, needWatch) != null){
                this.deleteNode(PARENT_PATH);
            }        
        }
        
        /**
         * 收到来自Server的Watcher通知后的处理。
         */
        @Override
        public void process(WatchedEvent event) {
            
            System.out.println("进入 process 。。。。。event = " + event);
            
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            if (event == null) {
                return;
            }
            
            // 连接状态
            KeeperState keeperState = event.getState();
            // 事件类型
            EventType eventType = event.getType();
            // 受影响的path
            String path = event.getPath();
            //原子对象seq 记录进入process的次数
            String logPrefix = "【Watcher-" + this.seq.incrementAndGet() + "】";
    
            System.out.println(logPrefix + "收到Watcher通知");
            System.out.println(logPrefix + "连接状态:	" + keeperState.toString());
            System.out.println(logPrefix + "事件类型:	" + eventType.toString());
    
            if (KeeperState.SyncConnected == keeperState) {
                // 成功连接上ZK服务器
                if (EventType.None == eventType) {
                    System.out.println(logPrefix + "成功连接上ZK服务器");
                    connectedSemaphore.countDown();
                } 
                //创建节点
                else if (EventType.NodeCreated == eventType) {
                    System.out.println(logPrefix + "节点创建");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } 
                //更新节点
                else if (EventType.NodeDataChanged == eventType) {
                    System.out.println(logPrefix + "节点数据更新");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } 
                //更新子节点
                else if (EventType.NodeChildrenChanged == eventType) {
                    System.out.println(logPrefix + "子节点变更");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } 
                //删除节点
                else if (EventType.NodeDeleted == eventType) {
                    System.out.println(logPrefix + "节点 " + path + " 被删除");
                }
                else ;
            } 
            else if (KeeperState.Disconnected == keeperState) {
                System.out.println(logPrefix + "与ZK服务器断开连接");
            } 
            else if (KeeperState.AuthFailed == keeperState) {
                System.out.println(logPrefix + "权限检查失败");
            } 
            else if (KeeperState.Expired == keeperState) {
                System.out.println(logPrefix + "会话失效");
            }
            else ;
    
            System.out.println("--------------------------------------------");
    
        }
    
        /**
         * <B>方法名称:</B>测试zookeeper监控<BR>
         * <B>概要说明:</B>主要测试watch功能<BR>
         * @param args
         * @throws Exception
         */
        public static void main(String[] args) throws Exception {
    
            //建立watcher //当前客户端可以称为一个watcher 观察者角色
            ZooKeeperWatcher zkWatch = new ZooKeeperWatcher();
            //创建连接 
            zkWatch.createConnection(CONNECTION_ADDR, SESSION_TIMEOUT);
            //System.out.println(zkWatch.zk.toString());
            
            Thread.sleep(1000);
            
            // 清理节点
            zkWatch.deleteAllTestPath(false);
            
            //-----------------第一步: 创建父节点 /p ------------------------//
            if (zkWatch.createPath(PARENT_PATH, System.currentTimeMillis() + "", true)) {
                
                Thread.sleep(1000);
                
                //-----------------第二步: 读取节点 /p 和    读取/p节点下的子节点(getChildren)的区别 --------------//
                // 读取数据
                zkWatch.readData(PARENT_PATH, true);
            
                // 读取子节点(监控childNodeChange事件)
                zkWatch.getChildren(PARENT_PATH, true);
    
                // 更新数据
                zkWatch.writeData(PARENT_PATH, System.currentTimeMillis() + "");
                
                Thread.sleep(1000);
                // 创建子节点
                zkWatch.createPath(CHILDREN_PATH, System.currentTimeMillis() + "", true);
                
                
                //-----------------第三步: 建立子节点的触发 --------------//
    //            zkWatch.createPath(CHILDREN_PATH + "/c1", System.currentTimeMillis() + "", true);
    //            zkWatch.createPath(CHILDREN_PATH + "/c1/c2", System.currentTimeMillis() + "", true);
                
                //-----------------第四步: 更新子节点数据的触发 --------------//
                //在进行修改之前,我们需要watch一下这个节点:
                Thread.sleep(1000);
                zkWatch.readData(CHILDREN_PATH, true);
                zkWatch.writeData(CHILDREN_PATH, System.currentTimeMillis() + "");
                
            }
            
            Thread.sleep(10000);
            // 清理节点
            zkWatch.deleteAllTestPath(false);
            
            
            Thread.sleep(10000);
            zkWatch.releaseConnection();
            
        }
    
    }

    输出结果:

    【Main】开始连接ZK服务器
    进入 process 。。。。。event = WatchedEvent state:SyncConnected type:None path:null
    【Watcher-1】收到Watcher通知
    【Watcher-1】连接状态:    SyncConnected
    【Watcher-1】事件类型:    None
    【Watcher-1】成功连接上ZK服务器
    --------------------------------------------
    进入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeCreated path:/p
    【Main】节点创建成功, Path: /p, content: 1572246563330
    【Watcher-2】收到Watcher通知
    【Watcher-2】连接状态:    SyncConnected
    【Watcher-2】事件类型:    NodeCreated
    【Watcher-2】节点创建
    --------------------------------------------
    读取数据操作...
    读取子节点操作...
    进入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeDataChanged path:/p
    【Main】更新数据成功,path:/p, stat: 25769803906,25769803907,1572246563261,1572246564283,1,0,0,0,13,0,25769803906
    
    【Watcher-3】收到Watcher通知
    【Watcher-3】连接状态:    SyncConnected
    【Watcher-3】事件类型:    NodeDataChanged
    【Watcher-3】节点数据更新
    --------------------------------------------
    进入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeCreated path:/p/c
    【Main】节点创建成功, Path: /p/c, content: 1572246565369
    【Watcher-4】收到Watcher通知
    【Watcher-4】连接状态:    SyncConnected
    【Watcher-4】事件类型:    NodeCreated
    【Watcher-4】节点创建
    --------------------------------------------
    进入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/p
    【Watcher-5】收到Watcher通知
    【Watcher-5】连接状态:    SyncConnected
    【Watcher-5】事件类型:    NodeChildrenChanged
    【Watcher-5】子节点变更
    读取数据操作...
    【Main】更新数据成功,path:/p/c, stat: 25769803908,25769803909,1572246565293,1572246566302,1,0,0,0,13,0,25769803908
    
    --------------------------------------------
    进入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeDataChanged path:/p/c
    【Watcher-6】收到Watcher通知
    【Watcher-6】连接状态:    SyncConnected
    【Watcher-6】事件类型:    NodeDataChanged
    【Watcher-6】节点数据更新
    --------------------------------------------
    【Main】删除节点成功,path:/p/c
    【Main】删除节点成功,path:/p

     

ZkClient API:

ZkClient对于Zookeeper原生API进行了封装

主要封装:

  1. 在session loss和session expire时自动创建新的ZooKeeper实例进行重连
  2. 将一次性watcher包装为持久watcher
  3. 创建与删除操作,可以进行递归操作
  1. 新建ZkClient对象,第一个参数ZkConnection对象,第二个参数连接超时时间
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.129:2181,192.168.238.131:2181,192.168.238.132:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 5000;//ms 
    ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 10000);
  2. 利用上文代码中的zkc对象,创建,获取,修改,删除节点操作.
    创建:
    两种创建节点的方式,创建持续节点的时候,第二个参数为是否递归创建,
    //创建暂时节点
    zkc.createEphemeral("/temp");
    //创建持续节点
    zkc.createPersistent("/super/c1", true);
    获取:
    zkc.readData方法:
    zkc.watchForChilds方法:
            zkc.createPersistent("/super", "1234");
            zkc.createPersistent("/super/c1", "c1内容");
            zkc.createPersistent("/super/c2", "c2内容");
            List<String> list = zkc.watchForChilds("/super");
            for(String p : list){
                System.out.println(p);
                String rp = "/super/" + p;
                String data = zkc.readData(rp);
                System.out.println("节点为:" + rp + ",内容为: " + data);
            }
    修改:
    zkc.writeData("/super","新内容",-1);
    删除:
    //非递归删除
    zkc.delete("/temp");
    //递归删除
    zkc.deleteRecursive("/super");
  3. 对父节点添加watcher监听(subscribeDataChanges())
    只监听删除与修改操作
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 5000;//ms
    ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 10000);
            
    zkc.createPersistent("/super", "1234");
            
    //对父节点添加监听变化。
    zkc.subscribeDataChanges("/super", new IZkDataListener() {
                @Override
                public void handleDataDeleted(String path) throws Exception {
                    System.out.println("删除的节点为:" + path);
                }
                
                @Override
                public void handleDataChange(String path, Object data) throws Exception {
                    System.out.println("变更的节点为:" + path + ", 变更内容为:" + data);
                }
    }); 
  4. 对子节点添加watcher监听(subscribeChildChanges())
    监听子节点的增删改操作
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 5000;//ms
    
    ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 10000);
            
    //对父节点添加监听子节点变化。
    zkc.subscribeChildChanges("/super", new IZkChildListener() {
                @Override
                public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                    System.out.println("parentPath: " + parentPath);
                    System.out.println("currentChilds: " + currentChilds);
                }
    });

Curator API

Curator是Netflix公司开源的一个Zookeeper客户端,后捐献给Apache,Curator框架在zookeeper原生API接口上进行了包装,解决了很多ZooKeeper客户端非常底层的细节开发。提供ZooKeeper各种应用场景( 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装,实现了Fluent风格(链式)的API接口

特性:

  1. 重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置一个重试策略,并且内部也提供了几种标准的重试策略(比如指数补偿)
  2. 连接状态监控: Curator初始化之后会一直对zk连接进行监听,一旦发现连接状态发生变化将会作出相应的处理
  3. zk客户端实例管理:Curator会对zk客户端到server集群的连接进行管理,并在需要的时候重建zk实例,保证与zk集群连接的可靠性
  4. 各种使用场景支持:Curator实现了zk支持的大部分使用场景(甚至包括zk自身不支持的场景),这些实现都遵循了zk的最佳实践,并考虑了各种极端情况

 

  1. 新建CuratorFramework对象
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 5000;//ms
    //1 重试策略:初试时间为1s 重试10次
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
    //2 通过工厂创建连接
    CuratorFramework cf = CuratorFrameworkFactory.builder()
                        .connectString(CONNECT_ADDR)
                        .sessionTimeoutMs(SESSION_OUTTIME)
                        .retryPolicy(retryPolicy)
                        .build();
    //3 开启连接
    cf.start();
  2. 利用上文代码中的cf对象,创建,获取,修改,删除节点操作.
    创建:
    可以递归创建,但需要添加creatingParentsIfNeeded()
    //4 建立节点 指定节点类型(不加withMode默认为持久类型节点)、路径、数据内容
    cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1","c1内容".getBytes());
    获取:
    //读取节点
    String ret1 = new String(cf.getData().forPath("/super/c2"));
    修改:
    //修改节点
    Stat stat = cf.setData().forPath("/super/c2", "修改c2内容".getBytes());
    删除:
    //使用该接口,只能删除叶子节点
    cf.delete().forPath("/super");
    //删除一个节点,并递归删除其所有子节点
    cf.delete().deletingChildrenIfNeeded().forPath("/super");
    //删除一个节点,强制指定版本进行删除
    cf.delete().withVersion(-1).forPath("/super");
    //删除一个节点,强制保证删除
    cf.delete().guaranteed().forPath("/super");
    //注意,guaranteed()接口是一个保障措施,只要客户端会话有效,那么Curator会在后台持续进行删除操作,直到节点删除成功。

    读取子节点与判断是否存在:

    List<String> list = cf.getChildren().forPath("/super");
            for(String p : list){
                System.out.println(p);
            }
            
            Stat stat = cf.checkExists().forPath("/super/c3");
            System.out.println(stat);
  3. 绑定回调函数
            ExecutorService pool = Executors.newCachedThreadPool();
            cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
            .inBackground(new BackgroundCallback() {
                @Override
                public void processResult(CuratorFramework cf, CuratorEvent ce) throws Exception {
                    System.out.println("code:" + ce.getResultCode());
                    System.out.println("type:" + ce.getType());
                    System.out.println("线程为:" + Thread.currentThread().getName());
                }
            }, pool)
            .forPath("/super/c3","c3内容".getBytes());
  4. 对父节点添加watcher监听(NodeCache)
    Curator的watcher是基于缓存实现的,缓存会定期向zookeeper获取最新数据到缓存中,供获取与校验(以空间换时间)
    触发事件为创建节点和更新节点,在删除节点的时候并不触发此操作
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 10000;//ms
            //1 重试策略:初试时间为1s 重试10次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
            //2 通过工厂创建连接
            CuratorFramework cf = CuratorFrameworkFactory.builder()
                        .connectString(CONNECT_ADDR)
                        .sessionTimeoutMs(SESSION_OUTTIME)
                        .retryPolicy(retryPolicy)
                        .build();
            
            //3 建立连接
            cf.start();
            
            //4 建立一个cache缓存
            final NodeCache cache = new NodeCache(cf, "/super", false);
            cache.start(true);
            cache.getListenable().addListener(new NodeCacheListener() {
                /**
                 * <B>方法名称:</B>nodeChanged<BR>
                 * <B>概要说明:</B>触发事件为创建节点和更新节点,在删除节点的时候并不触发此操作。<BR>
                 * @see org.apache.curator.framework.recipes.cache.NodeCacheListener#nodeChanged()
                 */
                @Override
                public void nodeChanged() throws Exception {
                    System.out.println("路径为:" + cache.getCurrentData().getPath());
                    System.out.println("数据为:" + new String(cache.getCurrentData().getData()));
                    System.out.println("状态为:" + cache.getCurrentData().getStat());
                    System.out.println("---------------------------------------");
                }
            });
  5. 对子节点添加watcher监听(PathChildrenCache)
    触发事件为增删改节点
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 10000;//ms
    //1 重试策略:初试时间为1s 重试10次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
            //2 通过工厂创建连接
            CuratorFramework cf = CuratorFrameworkFactory.builder()
                        .connectString(CONNECT_ADDR)
                        .sessionTimeoutMs(SESSION_OUTTIME)
                        .retryPolicy(retryPolicy)
                        .build();
            
            //3 建立连接
            cf.start();
            
            //4 建立一个PathChildrenCache缓存,第三个参数为是否接受节点数据内容 如果为false则不接受
            PathChildrenCache cache = new PathChildrenCache(cf, "/super", true);
            //5 在初始化的时候就进行缓存监听
            cache.start(StartMode.POST_INITIALIZED_EVENT);
            cache.getListenable().addListener(new PathChildrenCacheListener() {
                /**
                 * <B>方法名称:</B>监听子节点变更<BR>
                 * <B>概要说明:</B>新建、修改、删除<BR>
                 * @see org.apache.curator.framework.recipes.cache.PathChildrenCacheListener#childEvent(org.apache.curator.framework.CuratorFramework, org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent)
                 */
                @Override
                public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
                    switch (event.getType()) {
                    case CHILD_ADDED:
                        System.out.println("CHILD_ADDED :" + event.getData().getPath());
                        break;
                    case CHILD_UPDATED:
                        System.out.println("CHILD_UPDATED :" + event.getData().getPath());
                        break;
                    case CHILD_REMOVED:
                        System.out.println("CHILD_REMOVED :" + event.getData().getPath());
                        break;
                    default:
                        break;
                    }
                }
            });
  6. 分布式锁简单实现-共享锁(全局同步阻塞锁InterProcessMutex)
    主要利用Curator的InterProcessMutex实现
    for循环创建10个线程,利用CountDownLatch把十个线程阻塞在同一位置coutdown.await();,然后再同时释放阻塞coutdown.countDown();
    lock.acquire();获取锁
    lock.release();释放锁
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
    /** session超时时间 */
    static final int SESSION_OUTTIME = 5000;//ms 
    public static CuratorFramework createCuratorFramework(){
            //1 重试策略:初试时间为1s 重试10次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
            //2 通过工厂创建连接
            CuratorFramework cf = CuratorFrameworkFactory.builder()
                        .connectString(CONNECT_ADDR)
                        .sessionTimeoutMs(SESSION_OUTTIME)
                        .retryPolicy(retryPolicy)
                        .build();
            return cf;
        }
    
        public static void main(String[] args) throws Exception {
            final CountDownLatch coutdown = new CountDownLatch(1);
            for(int i = 0; i < 10; i++){
                new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        CuratorFramework cf = createCuratorFramework();
                        cf.start();
                        final InterProcessMutex lock = new InterProcessMutex(cf, "/super");try {
                            coutdown.await();
                            lock.acquire();
                            System.out.println(Thread.currentThread().getName() + "执行业务逻辑..");
                            Thread.sleep(1000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            try {
                                //释放
                                lock.release();
                                //reentrantLock.unlock();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                },"t" + i).start();
            }
            Thread.sleep(2000);
            coutdown.countDown();    
        }
  7. 分布式计数器(DistributedAtomicInteger)
    AtomicValue<Integer> value =atomicIntger.add(2);
    "更改状态 "  value.succeeded()
    "更改前 "  value.preValue()
    "更改后 "  value.postValue()
    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
    /** session超时时间 */
    
    static final int SESSION_OUTTIME = 5000;//ms
    
    static org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger atomicIntger =null;
    public static void main(String[] args) throws Exception {
            CountDownLatch countDown=new CountDownLatch(1);
    
            for(int i = 0; i < 5; i++){
                new Thread(new Runnable()  {
                    @Override
                    public void run() {
    
                        //1 重试策略:初试时间为1s 重试10次
                        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
                        //2 通过工厂创建连接
                        CuratorFramework cf = CuratorFrameworkFactory.builder()
                                .connectString(CONNECT_ADDR)
                                .sessionTimeoutMs(SESSION_OUTTIME)
                                .retryPolicy(retryPolicy)
                                .build();
                        //3 开启连接
                        cf.start();
                        try {
                            countDown.await();
    //4 使用DistributedAtomicInteger atomicIntger = new org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger(cf, "/super", new RetryNTimes(3, 1000)); System.out.println(Thread.currentThread().getName()+"开始增加"); AtomicValue<Integer> value =atomicIntger.add(2); System.out.println("更改状态 "+value.succeeded()); System.out.println("更改前 "+value.preValue()); System.out.println("更改后 "+value.postValue()); System.out.println("-------------------------------------------------"); } catch (Exception e) { e.printStackTrace(); } } },"t" + i).start(); } Thread.sleep(3000); countDown.countDown(); }

    输出结果:

    t0开始增加
    t3开始增加
    t1开始增加
    t2开始增加
    t4开始增加
    更改状态 true
    更改前 0
    更改后 2
    -------------------------------------------------
    更改状态 true
    更改前 2
    更改后 4
    -------------------------------------------------
    更改状态 true
    更改前 4
    更改后 6
    -------------------------------------------------
    更改状态 true
    更改前 6
    更改后 8
    -------------------------------------------------
    更改状态 true
    更改前 8
    更改后 10
    -------------------------------------------------
  8. 分布式Barrier(DistributedDoubleBarrier     DistributedBarrier)

    DistributedDoubleBarrier:

    barrier.enter();阻滞线程,只有当所有线程都串行执行到此处,才放行,让线程并行执行.
    barrier.leave();线程并行执行结束后,只有所有线程都执行结束,才放行,让线程串行执行

    /** zookeeper地址 */
        static final String CONNECT_ADDR = "192.168.238.129:2181,192.168.238.131:2181,192.168.238.132:2181";
        /** session超时时间 */
        static final int SESSION_OUTTIME = 5000;//ms 
        
        public static void main(String[] args) throws Exception { 
            for(int i = 0; i < 5; i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
                            CuratorFramework cf = CuratorFrameworkFactory.builder()
                                        .connectString(CONNECT_ADDR)
                                        .retryPolicy(retryPolicy)
                                        .build();
                            cf.start();
                            
                            DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(cf, "/super", 5);
                            Thread.sleep(1000 * (new Random()).nextInt(3));
                            System.out.println(Thread.currentThread().getName() + "已经准备");
                            barrier.enter();
                            System.out.println("同时开始运行...");
                            Thread.sleep(1000 * (new Random()).nextInt(3));
                            System.out.println(Thread.currentThread().getName() + "运行完毕");
                            barrier.leave();
                            System.out.println("同时退出运行...");
                            
    
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                },"t" + i).start();
            }   
        }

    输出结果:

    t3已经准备
    t1已经准备
    t2已经准备
    t4已经准备
    t0已经准备
    同时开始运行...
    同时开始运行...
    同时开始运行...
    同时开始运行...
    同时开始运行...
    t2运行完毕
    t3运行完毕
    t4运行完毕
    t1运行完毕
    t0运行完毕
    同时退出运行...
    同时退出运行...
    同时退出运行...
    同时退出运行...
    同时退出运行...

    DistributedBarrier:


    barrier.setBarrier();通过setBarrier()将新建的DistributedBarrier注入到barrier中
    barrier.waitOnBarrier();设置阻滞,等待barrier.removeBarrier();释放,所有线程继续运行

        /** zookeeper地址 */
        static final String CONNECT_ADDR = "192.168.238.131:2181,192.168.238.132:2181,192.168.1.129:2181";
        /** session超时时间 */
    
        static final int SESSION_OUTTIME = 1000;//ms
        
        static DistributedBarrier barrier = null;
        
        public static void main(String[] args) throws Exception {
            
            
            
            for(int i = 0; i < 3; i++){
                new Thread(new Runnable()  {
                    @Override
                    public void run() {
    
                            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
                            CuratorFramework cf = CuratorFrameworkFactory.builder()
                                        .connectString(CONNECT_ADDR)
                                        .sessionTimeoutMs(SESSION_OUTTIME)
                                        .retryPolicy(retryPolicy)
                                        .build();
                            cf.start();
                        try {
                            barrier = new DistributedBarrier(cf, "/s");
                            System.out.println(Thread.currentThread().getName() + "设置barrier!");
    
                            barrier.setBarrier();    //设置
                            barrier.waitOnBarrier();    //等待
    
                        System.out.println("---------开始执行程序----------");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
    
                    }
                },"t" + i).start();
            }
            //留给三个线程初始化连接的时间.不然barrier对象没有连接 空指针
            Thread.sleep(15000);
            barrier.removeBarrier();    //释放
            
            
        }

    输出结果:

    t1设置barrier!
    t0设置barrier!
    t2设置barrier!
    ---------开始执行程序----------
    ---------开始执行程序----------
    ---------开始执行程序----------


 

 

原文地址:https://www.cnblogs.com/yuming2018/p/11733874.html