概述
ZooKeeper的应用场景依赖于ZNode节点特性和Watch监听机制。
应用场景
数据发布/订阅
常用于实现配置中心,类似的有nacos。
数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到ZooKeeper的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。
配置信息一般有几个特点:
- 数据量小的KV
- 数据内容在运行时会发生动态变化
- 集群机器共享,配置一致
ZooKeeper采用的是推拉结合的方式。
- 推: 服务端会推给注册了监控节点的客户端Watcher事件通知,不会将值数据推给客户端
- 拉: 客户端获得通知后,然后主动到服务端拉取最新的数据
负载均衡
同一个服务下关联多个服务节点。
在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求。
命名服务
命名服务是为系统中的资源提供标识能力。ZooKeeper的命名服务主要是利用ZooKeeper节点的树形分层结构和子节点的顺序维护能力,来为分布式系统中的资源命名。
哪些应用场景需要用到分布式命名服务呢?典型的有:
- 分布式API目录
- 分布式节点命名
- 分布式ID生成器
分布式API目录(注册中心)
为分布式系统中各种API接口服务的名称、链接地址,提供类似JNDI(Java命名和目录接口)中的文件系统的功能。借助于ZooKeeper的树形分层结构就能提供分布式的API调用功能。
著名的Dubbo分布式框架就是应用了ZooKeeper的分布式的JNDI功能。在Dubbo中,使用ZooKeeper维护的全局服务接口API的地址列表。大致的思路为:
- 服务提供者(Service Provider)在启动的时候,向ZooKeeper上的指定节点
/dubbo/${serviceName}/providers
写入自己的API地址,这个操作就相当于服务的公开。 - 服务消费者(Consumer)启动的时候,订阅节点
/dubbo/{serviceName}/providers
下的服务提供者的URL地址,获得所有服务提供者的API。
分布式节点的命名
一个分布式系统通常会由很多的节点组成,节点的数量不是固定的,而是不断动态变化的。比如说,当业务不断膨胀和流量洪峰到来时,大量的节点可能会动态加入到集群中。而一旦流量洪峰过去了,就需要下线大量的节点。再比如说,由于机器或者网络的原因,一些节点会主动离开集群。
如何为大量的动态节点命名呢?一种简单的办法是可以通过配置文件,手动为每一个节点命名。但是,如果节点数据量太大,或者说变动频繁,手动命名则是不现实的,这就需要用到分布式节点的命名服务。
可用于生成集群节点的编号的方案:
- 使用数据库的自增ID特性,用数据表存储机器的MAC地址或者IP来维护。
- 使用ZooKeeper持久顺序节点的顺序特性来维护节点的NodeId编号。
在第2种方案中,集群节点命名服务的基本流程是:
- 启动节点服务,连接ZooKeeper,检查命名服务根节点是否存在,如果不存在,就创建系统的根节点。
- 在根节点下创建一个临时顺序ZNode节点,取回ZNode的编号把它作为分布式系统中节点的NODEID。
- 如果临时节点太多,可以根据需要删除临时顺序ZNode节点。
分布式ID生成器
在分布式系统中,分布式ID生成器的使用场景非常之多:
- 大量的数据记录,需要分布式ID。
- 大量的系统消息,需要分布式ID。
- 大量的请求日志,如restful的操作记录,需要唯一标识,以便进行后续的用户行为分析和调用链路分析。
- 分布式节点的命名服务,往往也需要分布式ID。
- 其他。。。
传统的数据库自增主键已经不能满足需求。在分布式系统环境中,迫切需要一种全新的唯一ID系统,这种系统需要满足以下需求:
- 全局唯一:不能出现重复ID。
- 高可用:ID生成系统是基础系统,被许多关键系统调用,一旦宕机,就会造成严重影响。
有哪些分布式的ID生成器方案呢?大致如下:
- Java的UUID,没有规律。
- 分布式缓存Redis生成ID:利用Redis的原子操作INCR和INCRBY,生成全局唯一的ID。
- Twitter的SnowFlake算法。
- ZooKeeper生成ID:利用ZooKeeper的顺序节点,生成全局唯一的ID。
- MongoDb的ObjectId:MongoDB是一个分布式的非结构化NoSQL数据库,每插入一条记录会自动生成全局唯一的一个"_id"字段值,它是一个12字节的字符串,可以作为分布式系统中全局唯一的ID。
基于Zookeeper实现分布式ID生成器:
在ZooKeeper节点的四种类型中,其中有以下两种类型具备自动编号的能力:
- PERSISTENT_SEQUENTIAL持久化顺序节点。
- EPHEMERAL_SEQUENTIAL临时顺序节点。
ZooKeeper的每一个节点都会为它的第一级子节点维护一份顺序编号,会记录每个子节点创建的先后顺序,这个顺序编号是分布式同步的,也是全局唯一的。
可以通过创建ZooKeeper的临时顺序节点的方法,生成全局唯一的ID。
示例:
java
@Slf4j
public class IDMaker extends CuratorBaseOperations {
private String createSeqNode(String pathPefix) throws Exception {
CuratorFramework curatorFramework = getCuratorFramework();
// 创建一个临时顺序节点
String destPath = curatorFramework.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(pathPefix);
return destPath;
}
public String makeId(String path) throws Exception {
String str = createSeqNode(path);
if(null != str){
// 获取末尾的序号
int index = str.lastIndexOf(path);
if(index>=0){
index+=path.length();
return index<=str.length() ? str.substring(index):"";
}
}
return str;
}
}
测试:
java
@Test
public void testMarkId() throws Exception {
IDMaker idMaker = new IDMaker();
idMaker.init();
String pathPrefix = "/idmarker/id-";
for(int i=0;i<5;i++){
new Thread(()->{
for (int j=0;j<10;j++){
String id = null;
try {
id = idMaker.makeId(pathPrefix);
log.info("{}线程第{}个创建的id为{}",Thread.currentThread().getName(),
j,id);
} catch (Exception e) {
e.printStackTrace();
}
}
},"thread"+i).start();
}
Thread.sleep(Integer.MAX_VALUE);
}
ZooKeeper实现雪花算法生产ID:
参考雪花算法文章。
传送门:https://blog.csdn.net/u010355502/article/details/132337630
分布式协调/通知
集群管理
分布式环境中,实时掌握每个节点的状态是必要的,可根据节点实时状态做出一些调整。
ZooKeeper可以实现实时监控节点状态变化:
- 可将节点信息写入ZooKeeper上的一个ZNode。
- 监听这个ZNode可获取它的实时状态变化。
- 场景
master监控wokers的状态。
- 实现
bash
# master服务
create /workers
# 让master服务监控/workers下的子节点
ls -w /workers
# worker1
create -e /workers/w1 "w1:001" # 创建子节点,master服务会收到子节点变化通知
# master服务
ls -w /workers # master需要再次监听
# worker2
create -e /workers/w2 "w2:001" # 创建子节点,master服务会收到子节点变化通知
# master服务
ls -w /workers # master需要再次监听
# worker2
quit # worker2退出,master服务会收到子节点变化通知
Master选举
- 场景
设计一个master-worker的组成员管理系统,要求系统中只能有一个master , master能实时获取系统中worker的情况。
- 实现
bash
# master1
create -e /master "m1:001"
# master2
create -e /master "m2:001" # /master已经存在,创建失败
Node already exists: /master
# 监听/master节点
stat -w /master
# master1删除后,master2收到如下通知
WATCHER::
WatchedEvent state:SyncConnected type:NodeDeleted path:/master
# 再次发起创建节点操作
create -e /master "m2:001"
分布式锁
参考ZooKeeper实现分布式锁文章。
传送门:https://blog.csdn.net/u010355502/article/details/132343432
分布式队列
顺序节点特性可实现队列的先进先出。
条件更新
- 场景
设想用2个客户端操作/c节点实现一个counter,使用set命令来实现自增1操作。条件更新场景∶
- 客户端1把/c更新到版本1,实现/c的自增1。
- 客户端2把/c更新到版本2,实现/c的自增1。
- 客户端1不知道/c已经被客户端⒉更新过了,还用过时的版本1去更新/c,更新失败。如果客户端1使用的是无条件更新,/c就会更新为2(其实应该是3),没有实现自增1。
- 实现
使用条件更新可以避免出现客户端基于过期的数据进行数据更新的操作。
bash
# 客户端1
get -s -w /c # 得到结果/c=0,version=0
# 客户端2
set -s -v 0 /c 1 # 设置有序节点版本号为0的值自增到1
# 客户端1会收到/c修改的通知
get -s -w /c # 得到结果/c=1,version=1
# 客户端1
set -s -v 0 /c 1 # 此时是更新不成功的,因为客户端2已经将/c的版本号更新为1了
客户端
ZooKeeper应用的开发主要通过Java客户端API去连接和操作ZooKeeper集群。可供选择的Java客户端API有:
- ZooKeeper官方的Java客户端API。
- 第三方的Java客户端API,比如Curator。
ZooKeeper官方的客户端API提供了基本的操作。例如,创建会话、创建节点、读取节点、更新数据、删除节点和检查节点是否存在等。不过,对于实际开发来说,ZooKeeper官方API有一些不足之处,具体如下:
- ZooKeeper的Watcher监测是一次性的,每次触发之后都需要重新进行注册。
- 会话超时之后没有实现重连机制。
- 异常处理烦琐,ZooKeeper提供了很多异常,对于开发人员来说可能根本不知道应该如何处理这些抛出的异常。
- 仅提供了简单的byte[]数组类型的接口,没有提供Java POJO级别的序列化数据处理接口。
- 创建节点时如果抛出异常,需要自行检查节点是否存在。
- 无法实现级联删除。
总之,ZooKeeper官方API功能比较简单,在实际开发过程中比较笨重,一般不推荐使用。
原生Java客户端使用
引入zookeeper client依赖:
java
<!-- zookeeper client -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
注意:保持与服务端版本一致,不然会有很多兼容性的问题。
ZooKeeper原生客户端主要使用org.apache.zookeeper.ZooKeeper
这个类来使用ZooKeeper服务。
ZooKeeper常用构造器:
java
ZooKeeper (connectString, sessionTimeout, watcher)
参数说明:
connectString:使用逗号分隔的列表,每个ZooKeeper节点是一个host.port对,host是机器名或者IP地址,port是ZooKeeper节点对客户端提供服务的端口号。客户端会任意选取connectString中的一个节点建立连接。
sessionTimeout:session timeout时间。
watcher:用于接收到来自ZooKeeper集群的事件。
使用zookeeper原生API连接zookeeper集群:
java
public class ZkClientDemo {
private static final String CONNECT_STR="localhost:2181";
private final static String CLUSTER_CONNECT_STR="192.168.65.156:2181,192.168.65.190:2181,192.168.65.200:2181";
public static void main(String[] args) throws Exception {
final CountDownLatch countDownLatch=new CountDownLatch(1);
ZooKeeper zooKeeper = new ZooKeeper(CLUSTER_CONNECT_STR,
4000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(Event.KeeperState.SyncConnected==event.getState()
&& event.getType()== Event.EventType.None){
// 如果收到了服务端的响应事件,连接成功
countDownLatch.countDown();
System.out.println("连接建立");
}
}
});
System.out.printf("连接中");
countDownLatch.await();
// CONNECTED
System.out.println(zooKeeper.getState());
// 创建持久节点
zooKeeper.create("/user", "jay".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
Zookeeper主要方法:
create(path, data, acl,createMode):创建一个给定路径的znode,并在znode保存data[]的数据,createMode指定znode的类型。
delete(path, version):如果给定path上的znode的版本和给定的version匹配,删除znode。
exists(path, watch):判断给定path上的znode是否存在,并在znode设置一个watch。
getData(path, watch):返回给定path上的znode数据,并在znode设置一个watch。
setData(path, data, version):如果给定path上的znode的版本和给定的version匹配,设置znode数据。
getChildren(path, watch):返回给定path上的znode的孩子znode名字,并在znode设置一个watch。
sync(path):把客户端session连接节点和leader节点进行同步。
方法特点:
- 所有获取znode数据的API都可以设置一个watch用来监控znode的变化。
- 所有更新znode数据的API都有两个版本:无条件更新版本和条件更新版本。如果version为-1,更新为无条件更新。否则只有给定的version和znode当前的version一样,才会进行更新,这样的更新是条件更新。
- 所有的方法都有同步和异步两个版本。同步版本的方法发送请求给ZooKeeper并等待服务器的响应。异步版本把请求放入客户端的请求队列,然后马上返回。异步版本通过callback来接受来自服务端的响应。
同步创建节点:
java
@Test
public void createTest() throws KeeperException, InterruptedException {
String path = zooKeeper.create(ZK_NODE, "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
log.info("created path: {}",path);
}
创建异步节点:
java
@Test
public void createAsycTest() throws InterruptedException {
zooKeeper.create(ZK_NODE, "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT,
(rc, path, ctx, name) -> log.info("rc {},path {},ctx {},name {}",rc,path,ctx,name),"context");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
修改节点数据:
java
@Test
public void setTest() throws KeeperException, InterruptedException {
Stat stat = new Stat();
byte[] data = zooKeeper.getData(ZK_NODE, false, stat);
log.info("修改前: {}",new String(data));
zooKeeper.setData(ZK_NODE, "changed!".getBytes(), stat.getVersion());
byte[] dataAfter = zooKeeper.getData(ZK_NODE, false, stat);
log.info("修改后: {}",new String(dataAfter));
}
Curator开源客户端使用
Curator是Netflix公司开源的一套ZooKeeper客户端框架,和ZkClient一样它解决了非常底层的细节开发工作,包括连接、重连、反复注册Watcher的问题以及NodeExistsException异常等。
Curator是Apache基金会的顶级项目之一,Curator具有更加完善的文档,另外还提供了一套易用性和可读性更强的Fluent风格的客户端API框架。
Curator还为ZooKeeper客户端框架提供了一些比较普遍的、开箱即用的、分布式开发用的解决方案,例如Recipe、共享锁服务、Master选举机制和分布式计算器等,帮助开发者避免了"重复造轮子"的无效开发工作。
在实际的开发场景中,使用Curator客户端就足以应付日常的ZooKeeper集群操作的需求。
引入依赖:
Curator包含了几个包:
- curator-framework是对ZooKeeper的底层API的一些封装。
- curator-client提供了一些客户端的操作,例如重试策略等。
- curator-recipes封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等。
java
<!-- zookeeper client -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
<!--curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
创建一个客户端实例:
在使用curator-framework包操作ZooKeeper前,首先要创建一个客户端实例。这是一个CuratorFramework类型的对象,有两种方法:
- 使用工厂类CuratorFrameworkFactory的静态newClient()方法。
java
// 重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3)
// 创建客户端实例
CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy);
// 启动客户端
client.start();
- 使用工厂类CuratorFrameworkFactory的静态builder构造者方法。
java
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.128.129:2181")
.sessionTimeoutMs(5000) // 会话超时时间
.connectionTimeoutMs(5000) // 连接超时时间
.retryPolicy(retryPolicy)
.namespace("base") // 包含隔离名称
.build();
client.start();
参数说明:
connectionString:服务器地址列表,在指定服务器地址列表的时候可以是一个地址,也可以是多个地址。如果是多个地址,那么每个服务器地址列表用逗号分隔, 如host1:port1,host2:port2,host3:port3。
retryPolicy:重试策略,当客户端异常退出或者与服务端失去连接的时候,可以通过设置客户端重新连接ZooKeeper服务端。而Curator提供了一次重试、多次重试等不同种类的实现方式。在Curator内部,可以通过判断服务器返回的keeperException的状态代码来判断是否进行重试处理,如果返回的是OK表示一切操作都没有问题,而SYSTEMERROR表示系统或服务端错误。
超时时间:Curator客户端创建过程中,有两个超时时间的设置。一个是sessionTimeoutMs会话超时时间,用来设置该条会话在ZooKeeper服务端的失效时间。另一个是connectionTimeoutMs客户端创建会话的超时时间,用来限制客户端发起一个会话连接到接收ZooKeeper服务端应答的时间。sessionTimeoutMs作用在服务端,而 connectionTimeoutMs作用在客户端。
retryPolicy如下选项:
策略名称 | 描述 |
---|---|
ExponentialBackoffRetry | 重试一组次数,重试之间的睡眠时间增加 |
RetryNTimes | 重试最大次数 |
RetryOneTime | 只重试一次 |
RetryUntilElapsed | 在给定的时间结束之前重试 |
创建节点:
创建节点的方式如下面的代码所示,回顾我们之前课程中讲到的内容,描述一个节点要包括节点的类型,即临时节点还是持久节点、节点的数据信息、节点是否是有序节点等属性和性质。
java
@Test
public void testCreate() throws Exception {
String path = curatorFramework.create().forPath("/curator-node");
curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/curator-node","some-data".getBytes())
log.info("curator create node :{} successfully.",path);
}
在Curator中,可以使用create函数创建数据节点,并通过withMod 函数指定节点类型(持久化节点,临时节点,顺序节点,临时顺序节点,持久化顺序节点等),默认是持久化节点,之后调用forPath函数来指定节点的路径和数据信息。
一次性创建带层级结构的节点:
java
@Test
public void testCreateWithParent() throws Exception {
String pathWithParent="/node-parent/sub-node-1";
String path = curatorFramework.create().creatingParentsIfNeeded().forPath(pathWithParent);
log.info("curator create node :{} successfully.",path);
}
获取数据:
java
@Test
public void testGetData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("get data from node :{} successfully.",new String(bytes));
}
更新节点:
我们通过客户端实例的setData()方法更新ZooKeeper服务上的数据节点,在setData方法的后边,通过forPath函数来指定更新的数据节点路径以及要更新的数据。
java
@Test
public void testSetData() throws Exception {
curatorFramework.setData().forPath("/curator-node","changed!".getBytes());
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("get data from node /curator-node :{} successfully.",new String(bytes));
}
删除节点:
java
@Test
public void testDelete() throws Exception {
String pathWithParent="/node-parent";
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(pathWithParent);
}
参数说明:
guaranteed:该函数的功能如字面意思一样,主要起到一个保障删除成功的作用,其底层工作方式是:只要该客户端的会话有效,就会在后台持续发起删除请求,直到该数据节点在ZooKeeper服务端被删除。
deletingChildrenIfNeeded:指定了该函数后,系统在删除该数据节点的时候会以递归的方式直接删除其子节点,以及子节点的子节点。
异步接口:
Curator 引入了BackgroundCallback接口,用来处理服务器端返回来的信息,这个处理过程是在异步线程中调用,默认在EventThread中调用,也可以自定义线程池。
java
public interface BackgroundCallback
{
/**
* Called when the async background operation completes
*
* @param client the client
* @param event operation result details
* @throws Exception errors
*/
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception;
}
如上接口,主要参数为client客户端和服务端事件event。
inBackground异步处理默认在EventThread中执行。
java
@Test
public void test() throws Exception {
curatorFramework.getData().inBackground((item1, item2) -> {
log.info(" background: {}", item2);
}).forPath(ZK_NODE);
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
指定线程池:
java
@Test
public void test() throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
curatorFramework.getData().inBackground((item1, item2) -> {
log.info(" background: {}", item2);
},executorService).forPath(ZK_NODE);
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
Curator监听器:
java
/**
* Receives notifications about errors and background events
*/
public interface CuratorListener
{
/**
* Called when a background task has completed or a watch has triggered
*
* @param client client
* @param event the event
* @throws Exception any errors
*/
public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception;
}
针对background通知和错误通知。使用此监听器之后,调用inBackground方法会异步获得监听。
Curator Caches:
Curator引入了Cache来实现对ZooKeeper服务端事件监听,Cache事件监听可以理解为一个本地缓存视图与远程 ZooKeeper视图的对比过程。Cache提供了反复注册的功能。Cache分为两类注册类型:节点监听和子节点监听。
- 节点监听
NodeCache对某一个节点进行监听:
java
public NodeCache(CuratorFramework client, String path)
Parameters:
client - the client
path - path to cache
可以通过注册监听器来实现,对当前节点数据变化的处理:
java
public void addListener(NodeCacheListener listener)
Add a change listener
Parameters:
listener - the listener
测试:
java
@Slf4j
public class NodeCacheTest extends AbstractCuratorTest{
public static final String NODE_CACHE="/node-cache";
@Test
public void testNodeCacheTest() throws Exception {
createIfNeed(NODE_CACHE);
NodeCache nodeCache = new NodeCache(curatorFramework, NODE_CACHE);
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{} path nodeChanged: ",NODE_CACHE);
printNodeData();
}
});
nodeCache.start();
}
public void printNodeData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath(NODE_CACHE);
log.info("data: {}",new String(bytes));
}
}
- 子节点监听
PathChildrenCache会对子节点进行监听,但是不会对二级子节点进行监听:
java
public PathChildrenCache(CuratorFramework client,
String path,
boolean cacheData)
Parameters:
client - the client
path - path to watch
cacheData - if true, node contents are cached in addition to the stat
可以通过注册监听器来实现,对当前节点的子节点数据变化的处理:
java
public void addListener(PathChildrenCacheListener listener)
Add a change listener
Parameters:
listener - the listener
测试:
java
@Slf4j
public class PathCacheTest extends AbstractCuratorTest{
public static final String PATH="/path-cache";
@Test
public void testPathCache() throws Exception {
createIfNeed(PATH);
PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework, PATH, true);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
log.info("event: {}",event);
}
});
// 如果设置为true则在首次启动时就会缓存节点内容到Cache中
pathChildrenCache.start(true);
}
}
TreeCache使用一个内部类TreeNode来维护这个一个树结构。并将这个树结构与ZK节点进行了映射。所以TreeCache可以监听当前节点下所有节点的事件。
java
public TreeCache(CuratorFramework client,
String path,
boolean cacheData)
Parameters:
client - the client
path - path to watch
cacheData - if true, node contents are cached in addition to the stat
可以通过注册监听器来实现,对当前节点的子节点,及递归子节点数据变化的处理:
java
public void addListener(TreeCacheListener listener)
Add a change listener
Parameters:
listener - the listener
测试:
java
@Slf4j
public class TreeCacheTest extends AbstractCuratorTest{
public static final String TREE_CACHE="/tree-path";
@Test
public void testTreeCache() throws Exception {
createIfNeed(TREE_CACHE);
TreeCache treeCache = new TreeCache(curatorFramework, TREE_CACHE);
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
log.info(" tree cache: {}",event);
}
});
treeCache.start();
}
}