Zookeeper Java API

1. 概述

  • 可以通过Java的AIP作为zookeeper客户端,实现与zookeeper服务器的交互。
  • 客户端应该遵循以下步骤,与服务器进行清晰和干净的交互
    • 连接到zookeeper服务器。zookeeper服务器为客户端分配会话ID。
    • 定期向服务器发送心跳。否则,zookeeper服务器将过期会话ID,客户端需要重新连接。
    • 只要会话ID处于活动状态,就可以获取/设置znode。
    • 所有任务完成后,断开与zookeeper服务器的连接。如果客户端长时间不活动,则 zookeeper服务器将自动断开客户端。

2. 准备工作

  • 导包
    
    <dependency>
        <groupId>org.apache.zookeepergroupId>
        <artifactId>zookeeperartifactId>
        <version>3.4.10version>
    dependency>
    
  • 默认使用log4j作为日志,写一个log4j的配置文件,叫log4j.properties
    # 全局日志配置
    log4j.rootLogger=INFO, stdout
    # 控制台输出
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
  • 写连接及断开连接代码。这段代码一般会通过Spring自动注入,但目前用测试,所以在测试类里面写好。@Before的代码,会在执行真正的test程序之前执行,@after则会在结束之后再执行。
  • 创建ZooKeeper需要提供ip地址及端口号、超时时间,再使用了一个监视器。由于这是异步执行的开启客户端,因此做一个阻塞操作,防止还没开启就执行后面的操作,在真正打开了客户端之后,发送一个消息,并解掉阻塞。
    public class GetTest {
      private final static String IP = "192.168.233.133:2181";  // ip及端口
      private final static Integer TIMEOUT = 5000;  // 超时时间,毫秒为单位
      private final static CountDownLatch countDownLatch = new CountDownLatch(1);
      private final static Logger log = Logger.getLogger(GetTest.class);
      private ZooKeeper zooKeeper;
      
      @Before
      public void before() throws IOException, InterruptedException {
        zooKeeper = new ZooKeeper(IP, TIMEOUT, new Watcher() {
          @Override
          public void process(WatchedEvent watchedEvent) {
            log.info("创建了连接");
            countDownLatch.countDown();
          }
        });
        countDownLatch.await();  //阻塞当前线程
      }
    
      @After
      public void after() throws InterruptedException {
        zooKeeper.close();
      }
    
    }
    
  • 至此,完成了准备工作,以后的增删改查等操作,只要在类里面新增测试方法即可。

3. 新增节点

// 同步方式 
create(String path, byte[] data, List<ACL> acl, CreateMode createMode) 
// 异步方式 
create(String path, byte[] data, List<ACL> acl, CreateMode createMode, AsyncCallback.StringCallback callBack,Object ctx)
  • pathznode路径。例如,/node1 /node1/node11
  • data:要存储在指定 znode 路径中的数据
  • acl :要创建的节点的访问控制列表。zookeeper API提供了一个静态接口 ZooDefs.Ids 来获取一些基本的acl列表。例如,ZooDefs.Ids.OPEN_ACL_UNSAFE 返回打开znodeacl列表。
  • createMode :节点的类型,这是一个枚举。
  • callBack:异步回调接口
  • ctx:传递上下文参数

3.1 ZooDefs 介绍

  • 用于保存ACL(权限信息)常量的类
    public class ZooDefs {
      public static final String[] opNames = new String[]{"notification", "create", "delete", "exists", "getData", "setData", "getACL", "setACL", "getChildren", "getChildren2", "getMaxChildren", "setMaxChildren", "ping"};
    
      public ZooDefs() {
      }
    
      public interface Ids {
        Id ANYONE_ID_UNSAFE = new Id("world", "anyone");
        Id AUTH_IDS = new Id("auth", "");
        ArrayList<ACL> OPEN_ACL_UNSAFE = new ArrayList(Collections.singletonList(new ACL(31, ANYONE_ID_UNSAFE)));
        ArrayList<ACL> CREATOR_ALL_ACL = new ArrayList(Collections.singletonList(new ACL(31, AUTH_IDS)));
        ArrayList<ACL> READ_ACL_UNSAFE = new ArrayList(Collections.singletonList(new ACL(1, ANYONE_ID_UNSAFE)));
      }
    
      public interface Perms {
        int READ = 1;
        int WRITE = 2;
        int CREATE = 4;
        int DELETE = 8;
        int ADMIN = 16;
        int ALL = 31;
      }
    
      public interface OpCode {
        int notification = 0;
        int create = 1;
        int delete = 2;
        int exists = 3;
        int getData = 4;
        int setData = 5;
        int getACL = 6;
        int setACL = 7;
        int getChildren = 8;
        int sync = 9;
        int ping = 11;
        int getChildren2 = 12;
        int check = 13;
        int multi = 14;
        int auth = 100;
        int setWatches = 101;
        int sasl = 102;
        int createSession = -10;
        int closeSession = -11;
        int error = -1;
      }
    }
    
  • ZooDefs.Ids.OPEN_ACL_UNSAFE:表示开放权限,所有用户拥有所有权限
  • ZooDefs.Ids.CREATOR_ALL_ACL:表示使用 auth 权限模式,并且对于满足条件的用户开放所有权限
  • ZooDefs.Ids.READ_ACL_UNSAFE:表示对于所有用户,只开放Read权限
  • ZooDefs.Ids.ANYONE_ID_UNSAFE:是一个常用的Id对象,表示所有用户
  • ZooDefs.Ids.AUTH_IDS:是一个Auth模式的Id对象,具体操作还要在后面演示。
  • 如上例子也可以看得出来,其实ACL的列表,其实也就是通过多个ACL组成的列表,每个ACL对象的参数也就是Id对象和ZooDefs.Perms 的权限参数组成。
  • 我们自己定制自己的权限,也可以通过这些常用量进行。

3.2 ZNode 类型的枚举类

  • ZNode可以分为持久无顺序、持久有顺序、临时无顺序、临时有顺序
  • 在创建新节点的时候,也需要予以定义,同样的,也有相关的枚举类
    public enum CreateMode {
      PERSISTENT(0, false, false),
      PERSISTENT_SEQUENTIAL(2, false, true),
      EPHEMERAL(1, true, false),
      EPHEMERAL_SEQUENTIAL(3, true, true);
    }
    
  • CreateMode.PERSISTENT:表示持久无顺序的节点
  • CreateMode.PERSISTENT_SEQUENTIAL:表示持久有顺序的节点
  • CreateMode.EPHEMERAL:表示临时无顺序的节点
  • CreateMode.EPHEMERAL_SEQUENTIAL:表示临时有顺序的节点

3.3 创建一个持久无顺序的开放权限节点

  • 该例子创建一个同步方法
      @Test
      public void testCreate1() throws KeeperException, InterruptedException {
        // 创建了一个开放权限,持久的节点
        // arg3:权限列表 world:anyone:cdrwa
        // arg4:节点类型 持久化节点
        zooKeeper.create("/hadoop/child", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      }
    

3.4 创建一个持久无顺序的读权限节点

  @Test
  public void testCreate2() throws KeeperException, InterruptedException {
    // Ids.READ_ACL_UNSAFE world:anyone:r
    zooKeeper.create("/hadoop/child1", "data".getBytes(), ZooDefs.Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);
  }

3.5 创建一个持久无顺序的读写权限节点

  @Test
  public void testCreate3() throws KeeperException, InterruptedException {
    Id id = ZooDefs.Ids.ANYONE_ID_UNSAFE;   // 所有用户
    List<ACL> acl = Arrays.asList(new ACL(ZooDefs.Perms.READ, id), new ACL(ZooDefs.Perms.WRITE, id));  // 自己合成权限,读写权限wr
    zooKeeper.create("/hadoop/child2", "data".getBytes(), acl, CreateMode.PERSISTENT);
  }

3.6 创建一个持久无顺序有IP权限的节点

  @Test
  public void testCreate4() throws KeeperException, InterruptedException {
    Id ip = new Id("ip", "192.168.233.133");  // 设置ip权限
    List<ACL> acl = Collections.singletonList(new ACL(ZooDefs.Perms.ALL, ip));   // 表示对指定ip开放全部权限
    zooKeeper.create("/hadoop/child3", "data".getBytes(), acl, CreateMode.PERSISTENT);
  }

3.7 创建一个持久有顺序的 auth 模式限制的节点

  @Test
  public void testCreate5() throws KeeperException, InterruptedException {
    zooKeeper.addAuthInfo("digest", "root:root".getBytes());  // 添加授权用户
    // 设置为auth授权模式。使用内置的方式,即允许所有权限
    // 设置为持久顺序节点,会将授权的用户添加进去
    zooKeeper.create("/hadoop/child4", "data".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT_SEQUENTIAL);
  }

值得一提的是,该方法需要调用addAuthInfo在zookeeper内部先注册一个用户。这里需要注意,不能忘了

3.8 创建一个临时 auth 模式读权限的节点

  @Test
  public void testCreate6() throws KeeperException, InterruptedException {
    // 自定义权限
    // 授权模式和授权对象
    zooKeeper.addAuthInfo("digest", "root:root".getBytes());  // 添加授权用户
    Id id = new Id("auth", "root");
    List<ACL> acl = Collections.singletonList(new ACL(ZooDefs.Perms.READ, id));
    zooKeeper.create("/hadoop/child5", "data".getBytes(), acl, CreateMode.EPHEMERAL);// 在关闭当前客户端之前,其他客户端可以查得到该节点,关闭客户端之后这个节点就销毁了
    Thread.sleep(10000);
  }

3.9 创建一个 digest 模式的节点

  @Test
  public void testCreate7() throws KeeperException, InterruptedException {
    // digest授权模式,需要提前计算密文
    Id digest = new Id("digest", "root:qiTlqPLK7XM2ht3HMn02qRpkKIE=");
    List<ACL> acl = Collections.singletonList(new ACL(ZooDefs.Perms.ALL, digest));
    zooKeeper.create("/hadoop/child6", "data".getBytes(), acl, CreateMode.PERSISTENT);// 在关闭当前客户端之前,其他客户端可以查得到该节点,关闭客户端之后这个节点就销毁了
  }

值得一提的是,需要提前计算好密文

3.10 异步的方法创建一个全权限节点

  @Test
  public void testCreate8() throws InterruptedException {
    // 创建了一个开放权限,持久的节点
    // arg3:权限列表 world:anyone:cdrwa
    // arg4:节点类型 持久化节点
    zooKeeper.create("/hadoop/child7", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new AsyncCallback.StringCallback() {
      @Override
      public void processResult(int rc, String path, Object ctx, String name) {
        log.info(rc);  // 0代表成功了
        log.info(path);  // 传进来的,添加的节点
        log.info(name);   // 真正查到的节点的名字
        log.info(ctx.toString());  // 上下文参数,ctx传进来的东西
        log.info("终于结束啦!");
        countDownLatch.countDown();  // 关闭等待
      }
    }, "ctx");
    countDownLatch.await();  // 拥塞,等待结束
  }

4. 更新节点

// 同步方式 
setData(String path, byte[] data, int version) 
// 异步方式 
setData(String path, byte[] data, int version,AsyncCallback.StatCallback callBack, Object ctx)
  • pathznode路径。例如,/node1 /node1/node11
  • data:要存储在指定 znode 路径中的数据
  • version:znode的当前版本。值为-1时,表示不需要考虑版本。如果指定版本之后,就可以做成一个乐观锁。
  • callBack:异步回调接口
  • ctx:传递上下文参数

4.1 更新一个节点(同步)

  @Test
  public void testSet1() throws KeeperException, InterruptedException {
    Stat stat = zooKeeper.setData("/hadoop", "newData".getBytes(), -1);  // 返回状态信息
    log.info(stat.toString());  // 将状态信息打印
    log.info("当前版本号" + stat.getVersion());
    log.info("节点创建时间" + stat.getCtime());
    log.info("节点修改时间" + stat.getMtime());
  }

4.2 更新一个节点(异步)

  @Test
  public void testSet2() throws InterruptedException {
    zooKeeper.setData("/hadoop", "newData".getBytes(), 4, new AsyncCallback.StatCallback() {   // 乐观锁
      @Override
      public void processResult(int rc, String path, Object ctx, Stat stat) {
        log.info(rc);  // 0 代表修改成功
        log.info(path); // 输入的节点路径
        log.info(stat.getVersion()); // 当前版本
        countDownLatch.countDown();  // 解放
      }
    }, null);  // 返回状态信息
    countDownLatch.await();   // 阻塞
  }

4.3 更新auth或digest权限的节点

  @Test
  public void testSet3() throws KeeperException, InterruptedException {
    zooKeeper.addAuthInfo("digest", "root:root".getBytes());   // 添加授权用户
    Stat stat = zooKeeper.setData("/hadoop/child4", "newData".getBytes(), -1);  // 对于有权限限制的节点,需要提前添加授权用户
    log.info(stat.toString());  // 将状态信息打印
    log.info("当前版本号" + stat.getVersion());
    log.info("节点创建时间" + stat.getCtime());
    log.info("节点修改时间" + stat.getMtime());
  }

更新该节点的时候,需要注意,要先添加用户,否则没有权限更新

5. 删除节点

// 同步方式 
delete(String path, int version) 
// 异步方式 
delete(String path, int version, AsyncCallback.VoidCallback callBack, Object ctx)
  • pathznode路径。例如,/node1 /node1/node11
  • version:znode的当前版本。值为-1时,表示不需要考虑版本。如果指定版本之后,就可以做成一个乐观锁。
  • callBack:异步回调接口
  • ctx:传递上下文参数

5.1 删除一个节点(同步)

  @Test
  public void testDelete1() throws KeeperException, InterruptedException {
    zooKeeper.delete("/hadoop/child1", -1);  // 如果节点不存在,会删除失败
  }

5.2 删除一个节点(异步)

  @Test
  public void testDelete2() throws KeeperException, InterruptedException {
    zooKeeper.delete("/hadoop/child40000000004", -1, new AsyncCallback.VoidCallback() {
      @Override
      public void processResult(int rc, String path, Object ctx) {
        log.info(rc);
        log.info(path);
      }
    }, null);  // 如果节点不存在,会删除失败
  }

6. 查看节点

// 同步方式 
getData(String path, boolean b, Stat stat) 
// 异步方式 
getData(String path, boolean b,AsyncCallback.DataCallback callBack, Object ctx)
  • pathznode路径。例如,/node1 /node1/node11
  • b:是否使用连接对象中注册的监视器
  • stat:返回znode的元数据
  • callBack:异步回调接口
  • ctx:传递上下文参数

6.1 查看一个节点(同步)

  @Test
  public void testGet1() throws KeeperException, InterruptedException {
    Stat stat = new Stat();
    byte[] data = zooKeeper.getData("/hadoop", false, stat);
    log.info("获取到的数据是:" + new String(data));
    log.info("当前节点的版本:" + stat.getVersion());
  }

6.2 查看一个节点(异步)

  @Test
  public void testGet2() throws InterruptedException {
    zooKeeper.getData("/hadoop", null, new AsyncCallback.DataCallback() {
      @Override
      public void processResult(int rc, String path, Object ctx, byte[] bytes, Stat stat) {
        log.info(rc);
        log.info(path);
        log.info(new String(bytes));
        log.info(stat.getVersion());
        countDownLatch.countDown();
      }
    }, null);
    countDownLatch.await(); // 拥塞
  }

7. 查看子节点

// 同步方式 
getChildren(String path, boolean b) 
// 异步方式 
getChildren(String path, boolean b,AsyncCallback.ChildrenCallback callBack,Object ctx)
  • pathznode路径。例如,/node1 /node1/node11
  • b:是否使用连接对象中注册的监视器
  • callBack:异步回调接口
  • ctx:传递上下文参数

7.1 获取所有子节点的名称(同步)

  @Test
  public void testChildren() throws KeeperException, InterruptedException {
    List<String> children = zooKeeper.getChildren("/hadoop", null);  // 此操作,类似于ls
    children.forEach(System.out::println);  // 获取所有子节点的名称
  }

7.2 获取所有子节点的名称(异步)

  @Test
  public void testChildren2() throws InterruptedException {
    zooKeeper.getChildren("/hadoop", null, new AsyncCallback.Children2Callback() {  // 此操作类似于ls2
      @Override
      public void processResult(int rc, String path, Object ctx, List<String> list, Stat stat) {
        log.info(rc);
        log.info(path);
        log.info(stat.getVersion());
        log.info(list.toString());
        countDownLatch.countDown();
      }
    }, null);
    countDownLatch.await();
  }

8. 检查节点是否存在

// 同步方法 
exists(String path, boolean b) 
// 异步方法 
exists(String path, boolean b,AsyncCallback.StatCallback callBack,Object ctx)
  • pathznode路径。例如,/node1 /node1/node11
  • b:是否使用连接对象中注册的监视器
  • callBack:异步回调接口
  • ctx:传递上下文参数

8.1 检查节点是否存在(同步)

  @Test
  public void existsTest() throws KeeperException, InterruptedException {
    Stat stat = zooKeeper.exists("/hadoop", null);  // 这操作,相当于stat
    log.info(stat.toString());
  }

8.2 检查节点是否存在(异步)

  @Test
  public void existsTest2() throws InterruptedException {
    zooKeeper.exists("/hadoop", null, new AsyncCallback.StatCallback() {
      @Override
      public void processResult(int rc, String path, Object ctx, Stat stat) {
        log.info(rc);
        log.info(path);
        log.info(stat.getVersion());
        log.info(stat.getCtime());
        countDownLatch.countDown();
      }
    }, null);
    countDownLatch.await();
  }

9. 监听器

9.1 概念

  • zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对 象,当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变 等),会实时、主动通知所有订阅者
  • zookeeper采用了Watcher机制实现数据的发布/订阅功能。该机制在被订阅对 象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻了客户端压力。
  • watcher机制实际上与观察者模式类似,也可看作是一种观察者模式在分布式场景下的实现方式

9.2 watcher架构

  • Watcher实现由三个部分组成:
    • Zookeeper服务端
    • Zookeeper客户端
    • 客户端的ZKWatchManager对象
  • 客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watch管理器中。当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程。
    Zookeeper Java API_第1张图片

9.3 watcher特性

  • 一次性:一旦触发就失效了,再次使用需要再次注册
  • 客户端顺序回调:watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个watcher回调逻辑不应该太多,以免影响别的watcher执行
  • 轻量级:WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容
  • 时效性:watcher只有在当前session彻底失效时才会无效,若在session有效期内 快速重连成功,则watcher依然存在,仍可接收到通知

9.4 Watcher接口

  • Watcher是一个接口,任何实现了Watcher接口的类就是一个新的Watcher。
  • Watcher内部包含了两个枚举类:KeeperState、EventType
    Zookeeper Java API_第2张图片

9.4.1 通知状态(KeeperState)

  • KeeperState是客户端与服务端连接状态发生变化时对应的通知类型。
    Zookeeper Java API_第3张图片

9.4.2 事件类型(EventType)

  • EventType是数据节点(znode)发生变化时对应的通知类型。
  • EventType变化时 KeeperState永远处于SyncConnected通知状态下;当KeeperState发生变化时, EventType永远为None
    Zookeeper Java API_第4张图片

9.5 捕获相应的事件

  • getexists类方法中,可以添加Watcher
  • 以下是调用的注册方法和可监听事件间的关系
    Zookeeper Java API_第5张图片
  • 简单来说,就是getData()方法的监听器可以监测节点的修改和删除;exists()的监听器可以监测节点的修改和删除外,还可以检测节点的创建;getChildren() 的监听器可以监听该节点的子节点的修改与删除。

9.6 注册Watcher

9.6.1 监听客户端与服务端的连接状态

  • 当连接状态发生修改的时候,会修改KeeperState的状态,而EventType会保持为None
  • 该监听器在实例化Zookeeper的客户端的时候就放入,当连接状态发生变化的时候就会触发,并执行 Watcher 中的方法。并可以重复工作(究竟为什么不是一次性的,这个还需要考察)。
9.6.1.1 实验
  • 首先实现了Watcher接口,在触发方法的时候,首先检查一下事件类型EventType是不是None(一般都是)。然后根据KeepState的状态,输出不同的日志。其中,当是连接成功的状态时,因为有可能外面的方法还在拥塞,因此顺便唤醒一下。
    public class ZKConnectionWatcher implements Watcher {
    
      private CountDownLatch countDownLatch = null;
      private static final Logger log = Logger.getLogger(ZKConnectionWatcher.class);
    
      public ZKConnectionWatcher() {
      }
    
      public ZKConnectionWatcher(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
      }
    
      @Override
      public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType() == Event.EventType.None) {   // 说明没有发生除了连接问题以外的事件
          switch (watchedEvent.getState()) {
            case SyncConnected:
              log.info("客户端与服务器正常连接");
              if (this.countDownLatch != null) this.countDownLatch.countDown();
              break;
            case Disconnected:
              log.info("客户端与服务器断开连接");
              break;
            case Expired:
              log.info("会话SESSION超时");
              break;
            case AuthFailed:
              log.info("身份认证失败");
              break;
          }
        }
      }
    
      public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
      }
    }
    
  • 调用时,与正常的调用一样。在初始化客户端的时候,实例化一个之前写的Watcher即可。最后加一个睡眠,可以有更多的测试。比如说,如果直接关闭zookeeper的服务器,就会触发方法,状态为Disconnected。如果addAuthInfodigest写错,状态就会变成AuthFailed
    public class ConnectWatcherTest {
      private final static String IP = "192.168.233.133:2181";  // ip及端口
      private final static Integer TIMEOUT = 5000;  // 超时时间,毫秒为单位
      private final static CountDownLatch countDownLatch = new CountDownLatch(1);
      private final static Logger log = Logger.getLogger(ConnectWatcherTest.class);
      private ZooKeeper zooKeeper;
    
      @Test
      public void testWatcher() throws IOException, InterruptedException, KeeperException {
        zooKeeper = new ZooKeeper(IP, TIMEOUT, new ZKConnectionWatcher(countDownLatch));
        countDownLatch.await();
        log.info("当前会话ID" + zooKeeper.getSessionId());
        zooKeeper.addAuthInfo("digest", "root:root".getBytes());
    
        Stat stat = new Stat();
        byte[] data = zooKeeper.getData("/hadoop/child3", false, stat);
        log.info("接收到的数据是:" + new String(data));
        Thread.sleep(5000);
        zooKeeper.close();
        log.info("结束");
      }
    }
    

值得一提的是,监听器可以重复触发。只要有变化,都会触发。

9.6.2 监听节点的状态

// 使用连接对象的监视器 
exists(String path, boolean b) 
// 自定义监视器 
exists(String path, Watcher w)

// NodeCreated:节点创建 
// NodeDeleted:节点删除 
// NodeDataChanged:节点内容发生变化
  • path:znode路径
  • b:是否使用连接对象中注册的监视器
  • w:监视器对象
9.6.2.1 实验一:使用连接对象中的监视器
public class ZKCommonWatcher implements Watcher {
  private CountDownLatch countDownLatch = null;
  private static final Logger log = Logger.getLogger(ZKConnectionWatcher.class);

  public ZKCommonWatcher() {
  }

  public ZKCommonWatcher(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
  }

  @Override
  public void process(WatchedEvent watchedEvent) {
    switch (watchedEvent.getType()) {
      case NodeCreated:
        log.info("节点创建了");
        break;
      case NodeDataChanged:
        log.info("节点数据被修改了");
        break;
      case NodeDeleted:
        log.info("节点被删除了");
        break;
      case None: {
        Event.KeeperState state = watchedEvent.getState();
        log.info(state);
        if (this.countDownLatch != null && state == Event.KeeperState.SyncConnected)
          this.countDownLatch.countDown();
        break;
      }
    }
  }

  public void setCountDownLatch(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
  }
}
public class ExistsWatcherTest {
  private final static String IP = "192.168.233.133:2181";  // ip及端口
  private final static Integer TIMEOUT = 5000;  // 超时时间,毫秒为单位
  private final static CountDownLatch countDownLatch = new CountDownLatch(1);
  private final static Logger log = Logger.getLogger(ConnectWatcherTest.class);
  private ZooKeeper zooKeeper;

  @Before
  public void before() throws IOException, InterruptedException {
    zooKeeper = new ZooKeeper(IP, TIMEOUT, new ZKCommonWatcher(countDownLatch));  // 实例化一个watcher
    countDownLatch.await();  //阻塞当前线程
  }
  @Test
  public void testExists() throws KeeperException, InterruptedException {
    Stat stat = zooKeeper.exists("/hadoop/child100", true);  // 使用在创建客户端时的watcher
    if (stat != null) log.info(stat.getVersion());
    Thread.sleep(100000);
  }
  @After
  public void after() throws InterruptedException {
    zooKeeper.close();
  }
}

在注册Exists监视器的时候,选择true,使用默认的连接对象的监视器。连接方面的监视可以多次触发,但是节点状态方面的触发只能有一次。

9.6.2.2 实验二:使用自定义的一次性的监视器
  • 其实也就是在exists中注册一个匿名的Watcher实现类,在方法中辨别事件类型,并根据不同的类型做出不同的处理。
  • 这个Watcher只能触发一次,触发完之后就不能再监测了。
  @Test
  public void testExists2() throws KeeperException, InterruptedException {
    // 一次性的,用完就没了
    Stat stat = zooKeeper.exists("/hadoop/child100", new Watcher() {
      @Override
      public void process(WatchedEvent watchedEvent) {
        switch (watchedEvent.getType()) {
          case NodeCreated:
            log.info("x节点创建了");
            break;
          case NodeDataChanged:
            log.info("x节点数据被修改了");
            break;
          case NodeDeleted:
            log.info("x节点被删除了");
            break;
        }
      }
    });
    if (stat != null) log.info(stat.getVersion());
    Thread.sleep(100000);
  }
9.6.2.3 实验三:使用多个自定义的一次性的监视器
  • 可以注册多个监视器对同一个节点的状态进行监视。而触发的执行顺序,会根据注册的顺序来执行。
    @Test
      public void testExists3() throws KeeperException, InterruptedException {
        // 注册多个监听器
        zooKeeper.exists("/hadoop/child100", new Watcher() {
          @Override
          public void process(WatchedEvent watchedEvent) {
            log.info("1");
            log.info(watchedEvent.getType());
          }
        });
    
        zooKeeper.exists("/hadoop/child100", new Watcher() {
          @Override
          public void process(WatchedEvent watchedEvent) {
            log.info("2");
            log.info(watchedEvent.getType());
          }
        });
        Thread.sleep(100000);
      }
    
9.6.2.4 实验四:使用可以多次使用的监视器
  • 其实监视器还是一次性的,只是在监视器处理结束之前,重新再注册一个新的监视器。
      @Test
      public void testExists4() throws KeeperException, InterruptedException {
        String path = "/hadoop/child100";
        // 重复使用,用完再注册一个新的
        Stat stat = zooKeeper.exists(path, new Watcher() {
          @Override
          public void process(WatchedEvent watchedEvent) {
            switch (watchedEvent.getType()) {
              case NodeCreated:
                log.info("x节点创建了");
                break;
              case NodeDataChanged:
                log.info("x节点数据被修改了");
                break;
              case NodeDeleted:
                log.info("x节点被删除了");
                break;
            }
            try {
              zooKeeper.exists(path, this);  // 走之前,又注册一遍
            } catch (KeeperException | InterruptedException e) {
              e.printStackTrace();
            }
          }
        });
        if (stat != null) log.info(stat.getVersion());
        Thread.sleep(100000);
      }
    
  • getData方法注册的监视器与上述方法类似,在此就不再赘述。

9.6.3 监听子节点的状态

// 使用连接对象的监视器 
getChildren(String path, boolean b) 
// 自定义监视器 
getChildren(String path, Watcher w)

// NodeChildrenChanged:子节点发生变化 
// NodeDeleted:节点删除
  • path:znode路径
  • b:是否使用连接对象中注册的监视器
  • w:监视器对象
9.6.3.1 实验一:使用自定义的子节点监视器
  • 使用连接对象的监视器实际上没什么区别,在此就不再演示。
  • 值得一提的是,该监测器只能检测到节点的增加或减少,不能监测到节点内容的改变。每次触发的事件类型都是NodeChildrenChanged
      @Test
      public void testChildrenWatcher() throws KeeperException, InterruptedException {
        String path = "/hadoop";
        List<String> children = zooKeeper.getChildren(path, new Watcher() {
          @Override
          public void process(WatchedEvent watchedEvent) {
            log.info("目前的状态是:" + watchedEvent.getState());
            log.info("发生了" + watchedEvent.getType() + "事件");
            try {
              zooKeeper.getChildren(path, this);  // 重新注册
            } catch (KeeperException | InterruptedException e) {
              e.printStackTrace();
            }
          }
        });
        Thread.sleep(100000);
      }
    

9.7 配置中心案例

  • zookeeper 保存数据库的配置信息。当配置信息发生修改的时候,提醒客户端的监听器配置值发生了变化,配置信息跟着更新一遍。
  • 用于保存数据库信息的配置类,可以通过输入一个Map,通过反射实现属性的配置。
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    public class DBConfig {
      private String ip;
      private String username;
      private String password;
      private static final Logger log = Logger.getLogger(DBConfig.class);
    
      public DBConfig(Map<String, String> config) throws NoSuchFieldException, IllegalAccessException {
        importConfig(config);
        log.info("创建DBConnection成功: " + this.toString());
      }
    
      public void importConfig(Map<String, String> config) throws NoSuchFieldException, IllegalAccessException {
        for (Map.Entry<String, String> entry : config.entrySet()) {
          String key = entry.getKey();
          key = key.substring(key.lastIndexOf("/") + 1);
          String value = entry.getValue();
          Field field = this.getClass().getDeclaredField(key);
          field.setAccessible(true);
          field.set(this, value);
        }
      }
    }
    
  • 实现监听器的配置中心。负责处理zookeeper与配置信息的关系。在连接成功的时候,会向zookeeper取值,放在config的键值对中,并将值赋予DBConfig对象;当有值发生变化时,会触发监听器,然后监听器会重新初始化DBConfig对象。实现监听值的修改。
    public class ConfigCenter implements Watcher, Closeable {
      private String ip = null;  // ip及端口
      private static Integer timeOut = 5000;  // 超时时间,毫秒为单位
      private final static CountDownLatch countDownLatch = new CountDownLatch(1);
      private final static Logger log = Logger.getLogger(ConfigCenter.class);
      private ZooKeeper zooKeeper = null;
      private DBConfig dbConfig = null;
      private final Map<String, String> config = new HashMap<>();
    
      public ConfigCenter() {
      }
    
      public ConfigCenter(String ip) throws IOException, InterruptedException {
        this(ip, timeOut);
      }
    
      public ConfigCenter(String ip, Integer timeOut) throws IOException, InterruptedException {
        this.ip = ip;
        ConfigCenter.timeOut = timeOut;
        initZK(ip, timeOut);
      }
    
      /**
       * 初始化zookeeper
       * @param ip
       * @param timeOut
       * @throws IOException
       * @throws InterruptedException
       */
      private void initZK(String ip, Integer timeOut) throws IOException, InterruptedException {
        zooKeeper = new ZooKeeper(ip, timeOut, this);
        countDownLatch.await();
      }
    
      /**
       * 编辑config的键值对,修改或增加某个键值对
       *
       * @param path
       * @param data
       * @return
       */
      private Map<String, String> editConfig(String path, byte[] data) {
        config.put(path, new String(data));
        return config;
      }
    
      /**
       * 初始化config键值对,在zookeeper中读出来
       *
       * @throws KeeperException
       * @throws InterruptedException
       */
      private void initConfig() throws KeeperException, InterruptedException {
        byte[] ip = zooKeeper.getData("/config/ip", true, null);   // 每次触发之后,会重新初始化监听器
        editConfig("/config/ip", ip);
        byte[] username = zooKeeper.getData("/config/username", true, null);
        editConfig("/config/username", username);
        byte[] password = zooKeeper.getData("/config/password", true, null);
        editConfig("/config/password", password);
      }
    
      /**
       * 读取zookeeper的配置值,并且放入到DBConnection中
       */
      private void initDB() {
        try {
          initConfig();
          if (dbConfig == null) {
            dbConfig = new DBConfig(config);
          } else {
            dbConfig.importConfig(config);
          }
          log.info("初始化DBConnection成功!");
        } catch (KeeperException | InterruptedException | NoSuchFieldException | IllegalAccessException e) {
          e.printStackTrace();
        }
      }
    
    
      @Override
      public void process(WatchedEvent watchedEvent) {
        // 如果是None 说明是连接方面的变化
        if (watchedEvent.getType() == Event.EventType.None) {
          switch (watchedEvent.getState()) {
            case SyncConnected:
              log.info("连接 " + ip + "成功");
              initDB();  // 初始化db
              countDownLatch.countDown();
              break;
            case Disconnected:
              log.error("连接 " + ip + "已断开");
              break;
            case Expired:
              log.error("连接 " + ip + "已超时,需要重新连接服务器端");
              try {   // 重新连接
                this.initZK(ip, timeOut);
              } catch (IOException | InterruptedException e) {
                e.printStackTrace();
              }
              break;
            case AuthFailed:
              log.error("身份验证失败");
              break;
          }
        } else {  // 如果不是连接方面的变化,那就是监视的节点发生了变化
          switch (watchedEvent.getType()) {
            case NodeDataChanged:
              log.info("配置的节点信息发生了变化,赶紧重新配置!");
              initDB();  // 因为配置发生了变化,因此需要重新初始化db
              break;
            case NodeCreated:
              log.info("添加了配置信息,目前的配置信息是");
              break;
          }
        }
      }
    
      @Override
      public void close() throws IOException {
        try {
          zooKeeper.close();
          log.info("zooKeeper已关闭");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    

你可能感兴趣的:(zookeeper)