Zookeeper学习:开源客户端 Curator

本文介绍了Netflix的Zookeeper客户端框架Curator,包括如何创建会话、创建和删除节点、获取及更新数据。Curator简化了Zookeeper的客户端开发,提供Fluent编程风格,并自动处理会话管理和节点操作。

1. Curator 简介

Curator 是 Netflix 公司开源的一套 Zookeeper 客户端框架,和ZKClient一样,Curator 解决了很多 Zookeeper 客户端非常底层的细节开发工作,包括连接重连,反复注册Watcher 和 NodeExistsException异常等,是最流行的Zookeeper客户端之一。从编码风格上来讲,它提供了基于 Fluent 的编程风格支持

2. 准备工作

添加依赖

<dependency>
	 <groupId>org.apache.curator</groupId>
	 <artifactId>curator-framework</artifactId>
	 <version>2.12.0</version>
 </dependency>

3. 创建会话

Curator的创建会话方式与原生的 API 和 ZkClient 的创建方式区别很大。Curator创建客户端是通过 CuratorFrameworkFactory 工厂类来实现的。具体如下∶

使用CuratorFramework这个工厂类的两个静态方法来创建一个客户端

public static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy)

public static CuratorFramework newClient(String connectString, intsessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy)

其中参数 RetryPolicy 提供重试策略的接口,可以让用户实现自定义的重试策略,默认提供了以下实现,分别为ExponentialBackoffRetry(基于backoff 的重连策略)、RetryNTimes(重连N次策略)、RetryForever(永远重试策略)

== 通过调用CuratorFramework中的start()方法来启动会话==

RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",retryPolicy);
client.start();
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",5000,1000,retryPolicy);
client.start();

其实进一步查看源代码可以得知,其实这两种方法内部实现一样,只是对外包装成不同的方法。它们的底层都是通过第三个方法 builder 来实现的

RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
private static CuratorFramework Client = CuratorFrameworkFactory.builder()
	.connectString("server1:2181,server2:2181,server3:2181")
	.sessionTimeoutMs(50000)
	.connectionTimeoutMs(30000)
	.retryPolicy(retryPolicy)
	.build();
client.start();

参数:

  • connectString∶ zk 的 server 地址,多个 server 之间使用英文逗号分隔开
  • connectionTimeoutMs∶连接超时时间,如上是30s,默认是15s
  • sessionTimeoutMs∶会话超时时间,如上是50s,默认是60s
  • retryPolicy∶失败重试策略
    • ExponentialBackoffRetry∶构造器含有三个参数 ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
      • baseSleepTimeMs∶初始的sleep时间,用于计算之后的每次重试的sleep时间,计算公式∶当前sleep时间 = baseSleepTimeMs * Math.max(1,random.nextInt(1 << (retryCount+1)))
      • maxRetries∶最大重试次数
      • maxSleepMs∶最大sleep时间,如果上述的当前sleep计算出来比这个大,那么sleep用这个时间,默认的最大时间是Integer.MAX_VALUE毫秒。
    • 其他,查看org.apache.curator.RetryPolicy接口的实现类
    • start()∶完成会话的创建
package com.study.curator;

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

public class CreateSession {

    // 创建会话
    public static void main(String[] args) {


        //不使用fluent编程风格
        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("127.0.0.1:2181", exponentialBackoffRetry);
        curatorFramework.start();
        System.out.println( "会话被建立了");

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();
        System.out.println("会话2创建了");
    }
}

需要注意的是session2会话含有隔离命名空间,即客户端对 Zookeeper上数据节点的任何操作都是相对 /base 目录进行的,这有利于实现不同的Zookeeper的业务之间的隔离

3. 创建节点

curator提供了一系列Fluent风格的接口,通过使用Fluent编程风格的接口,开发人员可以进行自由组合来完成各种类型节点的创建。

创建一个初始内容为空的节点

client.create().forPath(path);

Curator默认创建的是持久节点,内容为空。

创建一个包含内容的节点

client.create().forPath(path,"我是内容".getBytes());

Curator 和 ZkClient 不同的是依旧采用Zookeeper原生API的风格,内容使用byte[]作为方法参数。

递归创建父节点,并选择节点类型

client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);

creatingParentslfNeeded 这个接口非常有用,在使用 Zookeeper 的过程中,开发人员经常会碰到 NoNodeException 异常,其中一个可能的原因就是试图对一个不存在的父节点创建子节点。因此,开发人员不得不在每次创建节点之前,都判断一下该父节点是否存在——这个处理通常比较麻烦。在使用 Curator 之后,通过调用 creatingParentslfNeeded 接口,Curator就能够自动地递归创建所有需要的父节点。

package com.study.curator;

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.apache.zookeeper.CreateMode;

public class CreateNote_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {

        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();
        client.start();
        System.out.println("会话2创建了");

        // 创建节点
        String path = "/sys-curator/c1";
        String s = client.create().creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes());

        System.out.println("节点递归创建成功,该节点路径" + s);
    }
}

4. 删除节点

删除节点的方法也是基于Fluent方式来进行操作,不同类型的操作调用新增不同的方法调用即可。

  • 删除一个子节点
client.delete().forPath(path);
  • 删除节点并递归删除其子节点
client.delete().deletingChildrenIfNeeded().forPath(path);
  • 指定版本进行删除
client.delete().withVersion(1).forPath(path);

如果此版本已经不存在,则删除异常,异常信息如下。

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode =BadVersion for
  • 强制保证删除一个节点
client.delete().guaranteed().forPath(path);

只要客户端会话有效,那么Curator会在后台持续进行删除操作,直到节点删除成功。比如遇到一些网络异常的情况,此guaranteed的强制删除就会很有效果。

package com.study.curator;

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

public class DeleteNote_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {

        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        
        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();

        System.out.println("会话2创建了");

        // 删除节点
        String path = "/sys-curator";
        client.delete().deletingChildrenIfNeeded().withVersion(-1).forPath(path);
        System.out.println("删除成功,删除的节点" + path);
    }
}

5. 获取数据

获取节点数据内容API相当简单,同时Curator提供了传入一个Stat变量的方式来存储服务器端返回的最新的节点状态信息

// 普通查询
client.getData().forPath(path);
// 包含状态查询
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath(path);
package com.study.curator;

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.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

public class GetNote_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {

        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);

        // 创建节点
        String path = "/sys-curator/c1";
        String s = client.create().creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes());

        System.out.println("节点递归创建成功,该节点路径" + s);

        // 获取节点的数据内容及状态信息

        // 数据内容
        byte[] bytes = client.getData().forPath(path);
        System.out.println("获取到的节点数据内容:" + new String(bytes));

        // 状态信息
        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath(path);

        System.out.println("获取到的节点状态信息:" + stat );
    }
}

6. 更新数据

更新数据,如果未传入version参数,那么更新当前最新版本,如果传入version则更新指定version,如果version已经变更,则抛出异常。

// 普通更新
client.setData().forPath(path,"新内容".getBytes());
// 指定版本更新
client.setData().withVersion(1).forPath(path);

版本不一致异常信息:

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for
package com.study.curator;

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.apache.zookeeper.data.Stat;

public class UpdateNote_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {

        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);

        System.out.println("会话2创建了");

        // 创建节点
        String path = "/sys-curator/c1";

        // 获取节点的数据内容及状态信息

        // 数据内容
        byte[] bytes = client.getData().forPath(path);
        System.out.println("获取到的节点数据内容:" + new String(bytes));

        // 状态信息
        Stat stat = new Stat(); //0
        client.getData().storingStatIn(stat).forPath(path);

        System.out.println("获取到的节点状态信息:" + stat );

        // 更新节点内容 //1
        int version = client.setData().withVersion(stat.getVersion()).forPath(path, "修改内容1".getBytes()).getVersion();
        System.out.println("当前的最新版本是" + version);
        byte[] bytes2 = client.getData().forPath(path);
        System.out.println("修改后的节点数据内容:" + new String(bytes2));

        // BadVersionException
        client.setData().withVersion(stat.getVersion()).forPath(path,"修改内容2".getBytes());

    }
}

结果表明当携带数据版本不一致时,无法完成更新操作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值