Curator是Zookeeper的Java客户端库,官网为 https://curator.apache.org
。
CuratorCache
可以缓存节点信息,既可以缓存单个节点,也可以缓存子树(该节点及其子节点)。
此外,可以给 CuratorCache
注册listener,在发生相关事件时收到通知。
环境
- Ubuntu 22.04
- Zookeeper 3.7.1
- JDK 17.0.3.1
- IntelliJ IDEA 2022.1.3
- Curator 5.4.0
准备
创建Maven项目 test1215
。
打开 pom.xml
文件,添加如下依赖:
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
在 src/test/java
目录下,创建测试文件 Test1215.java
。
首先加上连接和关闭连接的测试:
package pkg1;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class Test1215 {
private CuratorFramework client;
@Before
public void testConnect() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
client = CuratorFrameworkFactory.builder()
.connectString("localhost:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.namespace("test1215")
.build();
client.start();
}
// quit
@After
public void testClose() {
if (client != null) {
client.close();
}
}
注:
- 本例是单机Zookeeper,所以connectionString是
localhost:2181
,如果是Zookeeper集群,则connectionString是localhost:2181,localhost:2182,localhost:2183
这样的。 - 在创建连接时,指定了namespace为
test1215
,表示所有的操作都是在/test1215
节点下操作的,否则默认是在根节点/
下操作的。
CuratorCache
监听
节点事件包括创建、设值、删除等,对应的Curator方法如下:
forCreates()
:创建forChanges()
:设值forCreatesAndChanges()
:创建和设值forDeletes()
:删除forAll()
:所有事件
默认情况下,listener监听整个子树(指定节点及其子节点)的事件,如下表所示:
forCreates() | forChanges() | forCreatesAndChanges() | forDeletes() | forAll() | |
---|---|---|---|---|---|
创建 | Y | N | Y | N | Y |
设值 | N | Y | Y | N | Y |
创建子节点 | Y | N | Y | N | Y |
为子节点设值 | N | Y | Y | N | Y |
创建子节点并为子节点设值 | N | Y | Y | N | Y |
删除子节点 | N | N | N | Y | Y |
删除节点 | N | N | N | Y | Y |
如果指定了 SINGLE_NODE_CACHE
选项,则只监听单个节点的事件,如下表所示:
forCreates() | forChanges() | forCreatesAndChanges() | forDeletes() | forAll() | |
---|---|---|---|---|---|
创建 | Y | N | Y | N | Y |
设值 | N | Y | Y | N | Y |
创建子节点 | N | N | N | N | N |
为子节点设值 | N | N | N | N | N |
创建子节点并为子节点设值 | N | N | N | N | N |
删除子节点 | N | N | N | N | N |
删除节点 | N | N | N | Y | Y |
注:如果删除多级节点,会触发多次。
下面是代码示例,使用的是默认选项,注释行使用的是 SINGLE_NODE_CACHE
选项。
@Test
public void test14() throws InterruptedException {
CuratorCache cache = CuratorCache.build(client,"/app1");
// CuratorCache cache = CuratorCache.build(client,"/app1", CuratorCache.Options.SINGLE_NODE_CACHE);
CuratorCacheListener listener = CuratorCacheListener.builder()
.forInitialized(() -> {
System.out.println("Initialized!");
})
.forCreates(childData -> System.out.println("Creates! " + childData))
.forChanges((childData, childData1) -> System.out.println("Changes! " + childData + ", " + childData1))
.forCreatesAndChanges((childData, childData1) -> System.out.println("CreatesAndChanges! " + childData + ", " + childData1))
.forDeletes(childData -> System.out.println("Deletes! " + childData))
.forAll((type, childData, childData1) -> System.out.println("All! " + type + ", " + childData + ", " + childData1))
.build();
cache.listenable().addListener(listener);
cache.start();
while (true) {
Thread.sleep(1000);
}
运行结果如下:
Initialized!
- 在命令行下创建节点
/test1215/app1
:
create /test1215/app1
程序输出如下:
Creates! ChildData{path='/app1', stat=198,198,1671163034141,1671163034141,0,0,0,0,0,0,198
, data=null}
CreatesAndChanges! null, ChildData{path='/app1', stat=198,198,1671163034141,1671163034141,0,0,0,0,0,0,198
, data=null}
All! NODE_CREATED, null, ChildData{path='/app1', stat=198,198,1671163034141,1671163034141,0,0,0,0,0,0,198
, data=null}
- 在命令行下设置节点
/test1215/app1
的值:
set /test1215/app1 hello
程序输出如下:
Changes! ChildData{path='/app1', stat=198,198,1671163034141,1671163034141,0,0,0,0,0,0,198
, data=null}, ChildData{path='/app1', stat=198,199,1671163034141,1671163303613,1,0,0,0,5,0,198
, data=[104, 101, 108, 108, 111]}
CreatesAndChanges! ChildData{path='/app1', stat=198,198,1671163034141,1671163034141,0,0,0,0,0,0,198
, data=null}, ChildData{path='/app1', stat=198,199,1671163034141,1671163303613,1,0,0,0,5,0,198
, data=[104, 101, 108, 108, 111]}
All! NODE_CHANGED, ChildData{path='/app1', stat=198,198,1671163034141,1671163034141,0,0,0,0,0,0,198
, data=null}, ChildData{path='/app1', stat=198,199,1671163034141,1671163303613,1,0,0,0,5,0,198
, data=[104, 101, 108, 108, 111]}
- 在命令行下创建节点
/test1215/app1/aaa
:
create /test1215/app1/aaa
程序输出如下:
Creates! ChildData{path='/app1/aaa', stat=200,200,1671163458175,1671163458175,0,0,0,0,0,0,200
, data=null}
CreatesAndChanges! null, ChildData{path='/app1/aaa', stat=200,200,1671163458175,1671163458175,0,0,0,0,0,0,200
, data=null}
All! NODE_CREATED, null, ChildData{path='/app1/aaa', stat=200,200,1671163458175,1671163458175,0,0,0,0,0,0,200
, data=null}
- 在命令行下创建节点
/test1215/app1/aaa/bbb
并设值:
create /test1215/app1/aaa/bbb good
程序输出如下:
Creates! ChildData{path='/app1/aaa/bbb', stat=210,210,1671164623094,1671164623094,0,0,0,0,4,0,210
, data=[103, 111, 111, 100]}
CreatesAndChanges! null, ChildData{path='/app1/aaa/bbb', stat=210,210,1671164623094,1671164623094,0,0,0,0,4,0,210
, data=[103, 111, 111, 100]}
All! NODE_CREATED, null, ChildData{path='/app1/aaa/bbb', stat=210,210,1671164623094,1671164623094,0,0,0,0,4,0,210
, data=[103, 111, 111, 100]}
- 在命令行下设置节点
/test1215/app1/aaa
的值:
set /test1215/app1/aaa China
程序输出如下:
Changes! ChildData{path='/app1/aaa', stat=200,200,1671163458175,1671163458175,0,0,0,0,0,0,200
, data=null}, ChildData{path='/app1/aaa', stat=200,202,1671163458175,1671163677440,1,1,0,0,5,1,201
, data=[67, 104, 105, 110, 97]}
CreatesAndChanges! ChildData{path='/app1/aaa', stat=200,200,1671163458175,1671163458175,0,0,0,0,0,0,200
, data=null}, ChildData{path='/app1/aaa', stat=200,202,1671163458175,1671163677440,1,1,0,0,5,1,201
, data=[67, 104, 105, 110, 97]}
All! NODE_CHANGED, ChildData{path='/app1/aaa', stat=200,200,1671163458175,1671163458175,0,0,0,0,0,0,200
, data=null}, ChildData{path='/app1/aaa', stat=200,202,1671163458175,1671163677440,1,1,0,0,5,1,201
, data=[67, 104, 105, 110, 97]}
- 在命令行下删除节点
/test1215/app1/aaa/bbb
:
delete /test1215/app1/aaa/bbb
程序输出如下:
Deletes! ChildData{path='/app1/aaa/bbb', stat=201,201,1671163592296,1671163592296,0,0,0,0,0,0,201
, data=null}
All! NODE_DELETED, ChildData{path='/app1/aaa/bbb', stat=201,201,1671163592296,1671163592296,0,0,0,0,0,0,201
, data=null}, null
- 在命令行下删除节点
/test1215/app1
:
deleteall /test1215/app1
程序输出如下:
Deletes! ChildData{path='/app1/aaa', stat=200,202,1671163458175,1671163677440,1,1,0,0,5,1,201
, data=[67, 104, 105, 110, 97]}
All! NODE_DELETED, ChildData{path='/app1/aaa', stat=200,202,1671163458175,1671163677440,1,1,0,0,5,1,201
, data=[67, 104, 105, 110, 97]}, null
Deletes! ChildData{path='/app1', stat=198,199,1671163034141,1671163303613,1,0,0,0,5,0,198
, data=[104, 101, 108, 108, 111]}
All! NODE_DELETED, ChildData{path='/app1', stat=198,199,1671163034141,1671163303613,1,0,0,0,5,0,198
, data=[104, 101, 108, 108, 111]}, null
旧式监听(略)
NodeCacheListener
:只监听单个节点PathChildrenCacheListener
:只监听子节点TreeCacheListener
:监听该节点以及子节点
缓存数据
与监听类似,默认缓存整个子树的数据,若指定 SINGLE_NODE_CACHE
选项,则只缓存单个节点的数据。
假设现有节点结构如下:
/
|-- test1215
|-- app1 (hello)
|-- aaa
|-- bbb (good)
|-- ccc
cache包含了整个子树的数据:
@Test
public void test15() throws InterruptedException {
CuratorCache cache = CuratorCache.build(client, "/app1");
cache.start();
Thread.sleep(1000);
cache.stream().forEach(System.out::println);
}
注:这里休眠了1秒钟,是为了避免cache还没来得及获取数据。
运行结果如下:
ChildData{path='/app1/aaa', stat=359,359,1671177661623,1671177661623,0,0,0,0,0,0,359
, data=null}
ChildData{path='/app1/bbb', stat=249,349,1671174130425,1671176678535,2,0,0,0,4,0,249
, data=[103, 111, 111, 100]}
ChildData{path='/app1/ccc', stat=354,354,1671177240115,1671177240115,0,0,0,0,0,0,354
, data=null}
ChildData{path='/app1', stat=237,240,1671173766690,1671173783899,1,31,0,0,5,3,359
, data=[104, 101, 108, 108, 111]}
如果指定 SINGLE_NODE_CACHE
选项,则cache只包含单个节点的数据:
@Test
public void test16() throws InterruptedException {
CuratorCache cache = CuratorCache.build(client, "/app1", CuratorCache.Options.SINGLE_NODE_CACHE);
cache.start();
Thread.sleep(1000);
cache.stream().forEach(System.out::println);
}
运行结果如下:
ChildData{path='/app1', stat=237,240,1671173766690,1671173783899,1,31,0,0,5,3,359
, data=[104, 101, 108, 108, 111]}
可以获取cache中指定节点的数据:
@Test
public void test17() throws InterruptedException {
CuratorCache cache = CuratorCache.build(client, "/app1");
cache.start();
Thread.sleep(1000);
Optional<ChildData> childData = cache.get("/app1/bbb");
childData.ifPresent(System.out::println);
}
注意不能指定 SINGLE_NODE_CACHE
选项,否则查询不到字节点。
运行结果如下:
ChildData{path='/app1/bbb', stat=249,349,1671174130425,1671176678535,2,0,0,0,4,0,249
, data=[103, 111, 111, 100]}