基于Curator操作ZooKeeper(二)-Watcher操作

版权声明: https://blog.csdn.net/Dongguabai/article/details/83038763

Java原生API操作ZooKeeper可参看:

Java原生API操作Zookeeper(一)

Java原生API操作Zookeeper(二)

相关内容:

基于Curator操作ZooKeeper(一)-基本操作

基于Curator操作ZooKeeper(二)-Watcher操作-补充TreeCache

usingWatcher()方法

ZooKeeper Watcher监听机制(数据变更的通知)(一)(应用)中介绍过,绑定事件只有三个操作:getData、exists、getChildren。

这个方法有两个重载的方法,实现这两个接口其实都差不多:

使用usingWatcher()方法监听只会触发一次,监听完毕后就会销毁。

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorOperator {

	public CuratorFramework client = null;
	public static final String zkServerPath = "192.168.220.135:2181,192.168.220.136:2181,192.168.220.137:2181";

	/**
	 * 实例化zk客户端
	 */
	public CuratorOperator() {
		/**
		 * 同步创建zk示例,原生api是异步的
		 * 
		 * curator链接zookeeper的重试策略:
		 *
		 * 1>ExponentialBackoffRetry【推荐】
			 * baseSleepTimeMs:初始sleep时间(ms)
			 * maxRetries:最大重试次数,超过时间就不链接了
			 * maxSleepMs:最大重试时间(ms)
		 *
		 * 给定一个初始sleep时间base5leep丁imeMs,在这个基础上结合重试次数,通过以下公式计算出当前需要sleep的时间:
		   当前sleep时间=baseSleepTimeMs*Math.max(1, random.nextInt(1<<(retryCount+1)))
		   可以看出,随着重试次数的增加,计算出的sleep时间会越来越大。如果该sleep时间在maxSleepMs的范围之内,那么就使用该sleep时间,否则使用maxSleepMs。另外,
		   maxRetries参数控制了最大重试次数,以避免无限制的重试。
		 */
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
		
		/**
		 * curator链接zookeeper的策略:
		 * 2>RetryNTimes【推荐】
			 * n:重试的次数
			 * sleepMsBetweenRetries:每次重试间隔的时间(ms)
		 */
//		RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
		
		/**
		 * curator链接zookeeper的策略:
		 * 3>RetryOneTime
		 * sleepMsBetweenRetry:只重试一次,重试间隔的时间
		 */
//		RetryPolicy retryPolicy2 = new RetryOneTime(3000);
		
		/**
		 * 4>
		 * 永远重试,不推荐使用
		 */
//		RetryPolicy retryPolicy3 = new RetryForever(retryIntervalMs)
		
		/**
		 * curator链接zookeeper的策略:
		 * 5>RetryUntilElapsed
		 * maxElapsedTimeMs:最大重试时间
		 * sleepMsBetweenRetries:每次重试间隔
		 * 重试时间超过maxElapsedTimeMs后,就不再重试
		 */
//		RetryPolicy retryPolicy4 = new RetryUntilElapsed(2000, 3000);

		//创建客户端
		client = CuratorFrameworkFactory.builder()  //builder
				.connectString(zkServerPath)
				.sessionTimeoutMs(10000)  //session超时时间
				.retryPolicy(retryPolicy)  //重试策略
				//namespace:
				.namespace("testCRUD")
				.build();
		/**
		 * CuratorFrameworkFactory工厂在创建出一个客户端CuratorFramework实例之后,实质上并没有完成会话的创建,而是需要调用
		   CuratorFramework的sta rt)方法来完成会话的创建。
		 */
		client.start();
	}
	
	/**
	 * 
	 * @Description: 关闭zk客户端连接
	 */
	public void closeZKClient() {
		if (client != null) {
			this.client.close();
		}
	}
	
	public static void main(String[] args) throws Exception {
		// 实例化
		CuratorOperator cto = new CuratorOperator();
		boolean isZkCuratorStarted = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        String nodePath = "/dongguabai/a";
        //创建节点
       /* byte[] data = "abcd".getBytes();
        cto.client.create()
                .creatingParentContainersIfNeeded()   //递归创建节点
                .withMode(CreateMode.PERSISTENT)      //节点模式
                .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) //ACL
                .forPath(nodePath,data); //不指定内容,则内容为空*/

        //获取节点
       /* byte[] bytes = cto.client.getData().forPath(nodePath);
        System.out.println("第一次获取节点数据为:"+new String(bytes));

        Stat stat = new Stat();
        byte[] bytes1 = cto.client.getData().storingStatIn(stat).forPath(nodePath);
        System.out.println("第二次获取节点数据为:"+new String(bytes1));
        System.out.println("获取的Stat为:"+ JsonUtil.toJSON(stat));
*/

       cto.client.getData().usingWatcher((CuratorWatcher) event -> {
		   System.out.println("触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
	   }).forPath(nodePath);
        //获取子节点
      /*  List<String> list = cto.client.getChildren().forPath(nodePath);
        System.out.println("开始打印子节点:");
        list.forEach(result-> System.out.println(result));
        System.out.println("打印结束!");*/

        //修改节点
      /*  Stat stat = cto.client.setData().forPath(nodePath,"new1".getBytes());
        System.out.println("第一次获取节点数据为:"+new String(cto.client.getData().forPath(nodePath)));

        Stat stat1 = cto.client.setData().withVersion(stat.getVersion()).forPath(nodePath, "new2".getBytes());
        System.out.println("第二次获取节点数据为:"+new String(cto.client.getData().forPath(nodePath)));*/

        //删除节点
       /* Stat stat = new Stat();
        byte[] bytes1 = cto.client.getData().storingStatIn(stat).forPath(nodePath);
        System.out.println("获取节点数据为:"+new String(bytes1));
        cto.client.delete()
                .guaranteed()  //防止网络抖动,只要客户端会话有效,那么Curator 会在后台持续进行删除操作,直到节点删除成功
                .deletingChildrenIfNeeded()  //如果有子节点会删除,注意除非人为删除namespace,否则namespace不会删除
                .withVersion(stat.getVersion())
                .forPath(nodePath);*/

        Thread.sleep(300000);
		
		cto.closeZKClient();
		boolean isZkCuratorStarted2 = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
	}
	
}

运行程序后,在客户端执行:

可以看到控制台输出了,可以看出没有输出namespace:

但是再在客户端执行相同的操作,控制台没有输出,说明事件只会触发一次。

ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便,需要开发人员自己反复注册Watcher,比较繁琐。Curator引入了Cache来实现对ZooKeeper服务端事件的监听。Cache是Curator中对事件监听的包装,其对事件的监听其实可以近似看作是一个本地缓存视图和远程ZooKeeper视图的对比过程。同时Curator能够自动为开发人员处理反复注册监听,从而大大简化了原生API开发的繁琐过程。Cache分为两类监听类型:节点监听和子节点监听。

NodeCache的使用

NodeCache有两个构造函数:

//构造NodeCache实例
		NodeCache nodeCache = new NodeCache(cto.client,nodePath);
		//建立Cache
		//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。一般在开发中我们会设置为true。
		nodeCache.start();

通过代码测试看看:

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorOperator2 {

	public CuratorFramework client = null;
	public static final String zkServerPath = "192.168.220.135:2181,192.168.220.136:2181,192.168.220.137:2181";

	public CuratorOperator2() {
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
		
		client = CuratorFrameworkFactory.builder()  //builder
				.connectString(zkServerPath)
				.sessionTimeoutMs(10000)  //session超时时间
				.retryPolicy(retryPolicy)  //重试策略
				//namespace:
				.namespace("testCRUD")
				.build();
		client.start();
//client.start(true);
	}
	
	public void closeZKClient() {
		if (client != null) {
			this.client.close();
		}
	}
	
	public static void main(String[] args) throws Exception {
		// 实例化
		CuratorOperator2 cto = new CuratorOperator2();
		boolean isZkCuratorStarted = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        String nodePath = "/dongguabai/a";

       cto.client.getData().usingWatcher((CuratorWatcher) event -> {
		   System.out.println("【使用usingWatcher】触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
	   }).forPath(nodePath);

       //NodeCache:监听数据节点的变更,会触发事件

		//构造NodeCache实例
		NodeCache nodeCache = new NodeCache(cto.client,nodePath);
		//建立Cache
		//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。
		nodeCache.start();
		if(nodeCache.getCurrentData()!=null){
			System.out.println("节点初始化数据为:"+new String(nodeCache.getCurrentData().getData()));
		}else {
			System.out.println("节点数据为空!");
		}

		cto.closeZKClient();
		boolean isZkCuratorStarted2 = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
	}
	
}

运行结果:

测试事件触发:

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorOperator2 {

	public CuratorFramework client = null;
	public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";

	public CuratorOperator2() {
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
		
		client = CuratorFrameworkFactory.builder()  //builder
				.connectString(zkServerPath)
				.sessionTimeoutMs(10000)  //session超时时间
				.retryPolicy(retryPolicy)  //重试策略
				//namespace:
				.namespace("testCRUD")
				.build();
		client.start();
	}
	
	public void closeZKClient() {
		if (client != null) {
			this.client.close();
		}
	}
	
	public static void main(String[] args) throws Exception {
		// 实例化
		CuratorOperator2 cto = new CuratorOperator2();
		boolean isZkCuratorStarted = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        String nodePath = "/dongguabai/a";

       cto.client.getData().usingWatcher((CuratorWatcher) event -> {
		   System.out.println("【使用usingWatcher】触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
	   }).forPath(nodePath);

       //NodeCache:监听数据节点的变更,会触发事件

		//构造NodeCache实例
		NodeCache nodeCache = new NodeCache(cto.client,nodePath);
		//建立Cache
		//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。
		nodeCache.start(true);
		if(nodeCache.getCurrentData()!=null){
			System.out.println("节点初始化数据为:"+new String(nodeCache.getCurrentData().getData()));
		}else {
			System.out.println("节点数据为空!");
		}

		//添加事件(也有remove),还可以知道Excutor
		nodeCache.getListenable().addListener(() -> {
			String data = new String(nodeCache.getCurrentData().getData());
			System.out.println("节点路径:"+nodeCache.getCurrentData().getPath()+",节点数据为:"+data);
		});

		System.in.read();
		cto.closeZKClient();
		boolean isZkCuratorStarted2 = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
	}
	
}

运行后在客户端操作:

控制台输出:

如果在客户端执行删除操作:

控制台输出,空指针是因为节点被删除了:

NodeCache不仅可以用于监听数据节点的内容变更,也能监听指定节点是否存在。如果原本节点不存在,那么Cache就会在节点被创建后触发NodeCacheListenera。但是,如果该数据节点被删除,那么Curator就无法触发NodeCacheListener了(后面版本已经修复了这个问题,网上有的资料这么说是有问题的)。

代码测试:

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;

import java.time.LocalDateTime;

public class CuratorOperator2 {

	public CuratorFramework client = null;
	public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";

	public CuratorOperator2() {
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
		
		client = CuratorFrameworkFactory.builder()  //builder
				.connectString(zkServerPath)
				.sessionTimeoutMs(10000)  //session超时时间
				.retryPolicy(retryPolicy)  //重试策略
				//namespace:
				.namespace("testCRUD")
				.build();
		client.start();
	}
	
	public void closeZKClient() {
		if (client != null) {
			this.client.close();
		}
	}
	
	public static void main(String[] args) throws Exception {
		// 实例化
		CuratorOperator2 cto = new CuratorOperator2();
		boolean isZkCuratorStarted = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

        String nodePath = "/dongguabai/a";

     /*  cto.client.getData().usingWatcher((CuratorWatcher) event -> {
		   System.out.println("【使用usingWatcher】触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
	   }).forPath(nodePath);*/

       //NodeCache:监听数据节点的变更,会触发事件

		//构造NodeCache实例
		NodeCache nodeCache = new NodeCache(cto.client,nodePath);
		//建立Cache
		//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。
		nodeCache.start(true);
		if(nodeCache.getCurrentData()!=null){
			System.out.println("节点初始化数据为:"+new String(nodeCache.getCurrentData().getData()));
		}else {
			System.out.println("节点数据为空!");
		}

		//添加事件(也有remove)
		nodeCache.getListenable().addListener(() -> {
			/*String data = new String(nodeCache.getCurrentData().getData());
			System.out.println("节点路径:"+nodeCache.getCurrentData().getPath()+",节点数据为:"+data);*/
			System.out.println(LocalDateTime.now()+"  |触发节点事件!!!");
		});

		System.out.println("开始创建节点!");
		//创建节点
		cto.client.create()
				.creatingParentContainersIfNeeded()   //递归创建节点
				.withMode(CreateMode.PERSISTENT)      //节点模式
				.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) //ACL
				.forPath(nodePath,"new Time".getBytes());

		System.in.read();
		cto.closeZKClient();
		boolean isZkCuratorStarted2 = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
	}
	
}

控制台输出:

在客户端操作:

控制台输出:

PathChildrenCache

有时候我们需要监听某一个节点具体的操作情况,这时候PathChildrenCache就派上用场了。PathChildrenCache用于监听指定ZooKeeper数据节点的子节点变化情况。我们要监听一个节点也没有被删除、新增、修改,我们只需要监听这个节点的父节点即可,即监听这个父节点以下所有的子节点,当某一个子节点发生了增删改操作的时候都会被监听到。

跟NodeCache类似,PathChildrenCache也需要调用start()来初始化:

数据准备:

同步异步测试:

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.List;

public class CuratorOperator3 {

	public CuratorFramework client = null;
	public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";

	public CuratorOperator3() {
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
		
		client = CuratorFrameworkFactory.builder()  //builder
				.connectString(zkServerPath)
				.sessionTimeoutMs(10000)  //session超时时间
				.retryPolicy(retryPolicy)  //重试策略
				//namespace:
				.namespace("testCRUD")
				.build();
		client.start();
	}
	
	public void closeZKClient() {
		if (client != null) {
			this.client.close();
		}
	}
	
	public static void main(String[] args) throws Exception {
		// 实例化
		CuratorOperator3 cto = new CuratorOperator3();
		boolean isZkCuratorStarted = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

		String nodePath = "/dongguabai/a";
		//为子节点添加watcher
		PathChildrenCache childrenCache = new PathChildrenCache(cto.client,nodePath,true);
		/**
		 * StartMode:初始化方式
		 *  NORMAL:普通异步初始化
		    BUILD_INITIAL_CACHE:同步初始化
		    POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
		 */
		childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
//		childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
//		childrenCache.start(PathChildrenCache.StartMode.NORMAL);
		List<ChildData> list = childrenCache.getCurrentData();
		System.out.println("获取子节点列表:");
		//如果是BUILD_INITIAL_CACHE可以获取这个数据,如果不是就不行
		list.forEach(childData -> {
			System.out.println(new String(childData.getData()));
		});


		System.in.read();
		cto.closeZKClient();
		boolean isZkCuratorStarted2 = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
	}
	
}

注册事件测试:

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicInteger;

public class CuratorOperator3 {

	public CuratorFramework client = null;
	public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";

	public CuratorOperator3() {
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
		
		client = CuratorFrameworkFactory.builder()  //builder
				.connectString(zkServerPath)
				.sessionTimeoutMs(10000)  //session超时时间
				.retryPolicy(retryPolicy)  //重试策略
				//namespace:
				.namespace("testCRUD")
				.build();
		client.start();
	}
	
	public void closeZKClient() {
		if (client != null) {
			this.client.close();
		}
	}
	
	public static void main(String[] args) throws Exception {
		// 实例化
		CuratorOperator3 cto = new CuratorOperator3();
		boolean isZkCuratorStarted = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));

		String nodePath = "/dongguabai/a";
		//为子节点添加watcher
		PathChildrenCache childrenCache = new PathChildrenCache(cto.client,nodePath,true);
		/**
		 * StartMode:初始化方式
		 *  NORMAL:普通异步初始化
		    BUILD_INITIAL_CACHE:同步初始化
		    POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件,而且所有的子节点的add操作都会来一遍这个也是比较坑的地方
		 */
//		childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
		childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
//		childrenCache.start(PathChildrenCache.StartMode.NORMAL);
		/*List<ChildData> list = childrenCache.getCurrentData();
		System.out.println("获取子节点列表:");
		//如果是BUILD_INITIAL_CACHE可以获取这个数据,如果不是就不行
		list.forEach(childData -> {
			System.out.println(new String(childData.getData()));
		});*/

		//注册事件,也可以加Excutor

		/**
		 * 当指定节点的子节点发生变化时,就会回调该方法oPathChildrenCacheEvent
		   类中定义了所有的事件类型,主要包括新增子节点(CHILD_ADDED)、子节点数据
		   变更(CHILD_UPDATED)和子节点删除(CHILD_REMOVED)三类。
		 */
		AtomicInteger atomicInteger = new AtomicInteger(0);
		childrenCache.getListenable().addListener(((client1, event) -> {
			atomicInteger.getAndIncrement();
			System.out.println("-----  "+LocalDateTime.now()+"  "+event.getType());
			if(event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)){
				System.out.println("子节点初始化成功...");
			}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)){
				String path = event.getData().getPath();
				System.out.println("添加子节点:" + event.getData().getPath());
				System.out.println("子节点数据:" + new String(event.getData().getData()));
			}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
				System.out.println("删除子节点:" + event.getData().getPath());
			}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
				System.out.println("修改子节点路径:" + event.getData().getPath());
				System.out.println("修改子节点数据:" + new String(event.getData().getData()));
			}

			//如果想指定节点可以判断路径
			/*String path = event.getData().getPath();
			if (path.equals(ADD_PATH)) {
				System.out.println("添加子节点:" + event.getData().getPath());
				System.out.println("子节点数据:" + new String(event.getData().getData()));
			} else if (path.equals("/super/imooc/e")) {
				System.out.println("添加不正确...");
			}*/
		}));

		Thread.sleep(1000);
		System.out.println("结果:"+atomicInteger);
		System.in.read();
		cto.closeZKClient();
		boolean isZkCuratorStarted2 = cto.client.isStarted();
		System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
	}
	
}

启动项目,先看控制台:

在客户端执行一些操作:

再看控制台:

猜你喜欢

转载自blog.csdn.net/Dongguabai/article/details/83038763