Curator基本操作(Zookeeper节点增删改查)

Curator是Zookeeper的Java客户端库,官网为 https://curator.apache.org

环境

  • 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 节点下操作的,否则默认是在根节点 / 下操作的。

增删改查操作

创建节点

    // create <node>
    @Test
    public void test1() throws Exception {
        client.create().forPath("/app1");
    }

运行测试,然后在命令行下查看:

ls /
[test1, test1215, test20000000003, test20000000004, test30000000005, zookeeper]
ls /test1215
[app1]

可见,创建了节点 /test1215/test1215/app1

get /test1215/app1
127.0.1.1

注意,如果不设置数据,默认会设置为机器的IP地址,即 127.0.0.1

若要显式设置数据,则 forPath() 方法需要多一个参数。

    // create <node> <data>
    @Test
    public void test2() throws Exception {
        client.create().forPath("/app2", "Hello".getBytes());
    }

若要设置节点的临时性和顺序性,则要用 withMode() 方法:

    // create -es <node>
    @Test
    public void test3() throws Exception {
        client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/app3");
    }

本例中创建了一个临时且顺序的节点。

注意,会话结束后,临时节点就会自动删除,所以要验证的话,需要设置断点,让程序暂停,然后在命令行下查看:

ls /test1215
[app1, app2, app30000000002]

可见,创建了顺序节点 app30000000002

等运行结束后再查看:

ls /test1215
[app1, app2]

可见,临时节点在会话结束之后自动删除了。

如果需要一次性创建多级节点,则要用 creatingParentsIfNeeded() 方法:

    // create <parent>
    // create <parent/child>
    @Test
    public void test4() throws Exception {
        client.create().creatingParentsIfNeeded().forPath("/app4/child1");

        client.create().creatingParentsIfNeeded().forPath("/app4/child2/aaa");
    }

注:貌似没有对应的命令,Curator可能是一级一级创建的。

运行测试,然后在命令行下查看:

ls /test1215
[app1, app2, app4]
ls /test1215/app4
[child1, child2]
ls /test1215/app4/child2
[aaa]

注意:默认的数据 127.0.0.1 是设置在最小的节点上的,本例中为 /test1215/app4/child1/test1215/app4/child2/aaa

查询节点

获取节点数据:

    // get <node>
    @Test
    public void test5() throws Exception {
        byte[] data = client.getData().forPath("/app2");

        String str = new String(data);

        System.out.println(str);
    }

运行结果如下:

Hello

查询子节点:

    // ls <node>
    @Test
    public void test6() throws Exception {
        List<String> list = client.getChildren().forPath("/");
        list.forEach(System.out::println);
    }

运行结果如下:

app2
app1
app4

注意:这里的 / 实际上是 /test1215 ,因为设置了namespace。

查询节点状态:

    // ls -s <node>
    @Test
    public void test7() throws Exception {
        Stat stat = new Stat();

        System.out.println(stat);

        client.getData().storingStatIn(stat).forPath("/app2");

        System.out.println(stat);
    }

运行结果如下:

0,0,0,0,0,0,0,0,0,0,0

103,103,1671156504367,1671156504367,0,0,0,0,5,0,103

设置节点数据

直接更新节点数据:

    // set <node> <data>
    @Test
    public void test8() throws Exception {
        client.setData().forPath("/app2", "China".getBytes());
    }

运行测试,然后在命令行下查看:

get /test1215/app2
China

根据版本号更新数据:

    // set -v <version> <node> <data>
    @Test
    public void test9() throws Exception {
        Stat stat = new Stat();

        client.getData().storingStatIn(stat).forPath("/app2");

        int version = stat.getVersion();

        System.out.println("Version = " + version);

        client.setData().withVersion(version).forPath("/app2", "good".getBytes());
//        client.setData().withVersion(100).forPath("/app2", "good".getBytes());
    }

运行结果如下:

Version = 1

注意:本例中,先获取节点的版本,在更新数据时,带上该版本号,以确保没有并发问题。如果此时实际的版本号已改变,二者不相等,则不会更新数据,而是抛出异常。

更新成功后版本号会加1。本例中,原先的版本号为1,如果更新成功,则版本号会变成2。

在命令行验证版本号:

ls -s /test1215/app2
[]
cZxid = 0x67
ctime = Fri Dec 16 10:08:24 CST 2022
mZxid = 0x80
mtime = Fri Dec 16 10:25:16 CST 2022
pZxid = 0x67
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0

可见,当前的 dataVersion 已经变为2。

如果更新时,获取的版本号和实际版本号不相等(本例中可用硬编码来指定一个错误的版本号),则会报错:

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /test1215/app2

删除节点

删除没有子节点的节点:

    // delete <node>
    @Test
    public void test10() throws Exception {
        client.delete().forPath("/app1");
    }

运行测试,然后在命令行下查看:

ls /test1215
[app2, app4]

可见 /test1215/app1 已经删除了。

删除带有子节点的节点:

    // deleteall <node>
    @Test
    public void test11() throws Exception {
        client.delete().deletingChildrenIfNeeded().forPath("/app4/child2");
    }

运行测试,然后在命令行下查看:

ls /test1215/app4
[child1]

可见, /test1215/app4/child2 连同其子节点 /test1215/app4/child2/aaa 一起删除了。

确保删除成功:

    // delete <node>
    @Test
    public void test12() throws Exception {
        client.delete().guaranteed().forPath("/app4/child1");
    }

运行测试,然后在命令行下查看:

ls /test1215/app4
[]

本例中看不出 guaranteed() 的效果。

可以在删除节点时,添加一个回调函数:

    // delete <node>
    @Test
    public void test13() throws Exception {
        client.delete().inBackground(new BackgroundCallback() {
            @Override
            public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                System.out.println("Deleted");
                System.out.println(curatorEvent);
            }
        }).forPath("/app2");
    }

运行结果如下:

Deleted
CuratorEventImpl{type=DELETE, resultCode=0, path='/app2', name='null', children=null, context=null, stat=null, data=null, watchedEvent=null, aclList=null, opResults=null}

在命令行下查看:

[zk: localhost:2181(CONNECTED) 107] ls /test1215
[app4]

可见 /test1215/app2 已经删除了。

注意,此时如果把 /test1215/app4 删除,则 /test1215 节点为空,大概几秒钟后也就自动删除了。但是刚才把 /test1215/app4 下的所有子节点都删除之后, /test1215/app4 节点为空,并不会自动删除。可见用namespace创建的节点有一个特性:当其为空时,就会自动删除。这应该也是封装的Zookeeper的特性吧,有待研究。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用 Java 原生 ZooKeeper 客户端实现对 ZooKeeper 服务端的增删改查,并监听节点变化同步到另一个 ZooKeeper 的示例代码: ```java import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.List; public class ZooKeeperClient implements Watcher { private static final String ZK1_ADDRESS = "zookeeper1:2181"; private static final String ZK2_ADDRESS = "zookeeper2:2181"; private static final String ZK1_PATH = "/path/to/zookeeper1"; private static final String ZK2_PATH = "/path/to/zookeeper2"; private ZooKeeper zk1; private ZooKeeper zk2; public ZooKeeperClient() throws IOException { // 连接到 zookeeper1 和 zookeeper2 zk1 = new ZooKeeper(ZK1_ADDRESS, 5000, this); zk2 = new ZooKeeper(ZK2_ADDRESS, 5000, this); } public void createNode(String path, byte[] data) throws KeeperException, InterruptedException { // 在 zookeeper1 创建节点,并将对应的数据同步到 zookeeper2 zk1.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk2.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } public byte[] getNodeData(String path) throws KeeperException, InterruptedException { // 从 zookeeper1 获取节点的数据 return zk1.getData(path, false, null); } public void updateNode(String path, byte[] data) throws KeeperException, InterruptedException { // 更新 zookeeper1 中节点的数据,并将更新后的数据同步到 zookeeper2 zk1.setData(path, data, -1); zk2.setData(path, data, -1); } public void deleteNode(String path) throws KeeperException, InterruptedException { // 删除 zookeeper1 中的节点,并删除相应的节点zookeeper2 zk1.delete(path, -1); zk2.delete(path, -1); } public List<String> getChildren(String path) throws KeeperException, InterruptedException { // 获取 zookeeper1 中节点的子节点列表 return zk1.getChildren(path, false); } @Override public void process(WatchedEvent event) { // 监听节点变化,并将变化后的数据同步到 zookeeper2 if (event.getType() == Event.EventType.NodeDataChanged) { String path = event.getPath(); try { byte[] data = zk1.getData(path, false, null); zk2.setData(path, data, -1); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } } } public void close() throws InterruptedException { // 关闭客户端连接 zk1.close(); zk2.close(); } public static void main(String[] args) throws Exception { ZooKeeperClient client = new ZooKeeperClient(); // 创建节点并设置数据 client.createNode(ZK1_PATH, "Hello, ZooKeeper!".getBytes()); // 获取节点数据 byte[] data = client.getNodeData(ZK1_PATH); System.out.println("Node data: " + new String(data)); // 更新节点数据 client.updateNode(ZK1_PATH, "Hello, Updated ZooKeeper!".getBytes()); // 再次获取节点数据 data = client.getNodeData(ZK1_PATH); System.out.println("Updated node data: " + new String(data)); // 删除节点 client.deleteNode(ZK1_PATH); // 获取子节点列表 List<String> children = client.getChildren("/"); System.out.println("Children nodes: " + children); // 保持程序运行 Thread.sleep(Long.MAX_VALUE); client.close(); } } ``` 上述代码中,我们使用了 Java 原生 ZooKeeper 客户端来实现对 ZooKeeper 服务端的增删改查操作。创建了一个 `ZooKeeperClient` 类,其中包含了对应的方法用于实现对 ZooKeeper 的操作。同时,该类实现了 `Watcher` 接口,用于监听节点变化并将变化后的数据同步到另一个 ZooKeeper。 在 `main` 方法中,我们创建了一个 `ZooKeeperClient` 对象,然后调用相应的方法进行增删改查操作。同时,我们也可以看到在监听器中的 `process` 方法中处理了节点数据变化的逻辑。 注意:这只是一个简单示例,实际应用中需要根据情况处理更多的异常和错误情况。 希望这能帮助到你!如果有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值