Curator是Netflix公司开源的一套Zookeeper客户端框架,是Apache的顶级项目,是全世界范围内使用最广泛的Zookeeper客户端之一。“Guava is to Java what Curator is to ZooKeeper”,可见Curator之重要,本文对Curator的创建,查看,更改,删除等操作进行详细的描述并配以案例,后续利用Curator来实现zookeeper的几个功能:leader选举,分布式锁,分布式队列等。
maven依赖
本文基于curator 2.12进行各个易用api的介绍和程序实战:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
创建session
api
Curator使用两个静态方法进行客户端创建,然后通过start方法启动客户端,另外还可以通过fluent风格进行client的创建,实质是newClient这两个方法也是通过fluent方式创建的client。
public static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy)
public static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy) {
return builder().
connectString(connectString).
sessionTimeoutMs(sessionTimeoutMs).
connectionTimeoutMs(connectionTimeoutMs).
retryPolicy(retryPolicy).
build();
}
参数解析:
- connnectString,sessionTimeoutMs,connectionTimeoutMs: 同原生api一样,分别是zk的服务器地址,用,分割;会话超时时间以及连接超时时间
- retryPolicy:重试策略,可以自定义重试策略,也可以用系统提供的几种重试策略: ExponentialBackoffRetry, RetryNTimes,RetryOneTime,BoundedExponentialBackoffRetry.
当然可以自定义实现重试策略,只需要实现RetryPolicy
接口:
// retryCount:重试的次数
// elapsedTimeMs:从第一次尝试已经花费的时间
// sleeper: 用于sleep的时间
public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper);
实战
-
程序实例
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 CuratorZKClient { public static void main(String[] args) throws InterruptedException { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 2); // fluent风格创建client CuratorFramework fluentClient = CuratorFrameworkFactory .builder() .connectString("localhost:2181") .sessionTimeoutMs(5000) .retryPolicy(retryPolicy) .build(); fluentClient.start(); System.out.println(fluentClient.getState()); // 非fluent风格创建client CuratorFramework client = CuratorFrameworkFactory. newClient("localhost:2181", 5000, 5000, retryPolicy); client.start(); System.out.println(fluentClient.getState()); Thread.sleep(Integer.MAX_VALUE); } }
-
运行结果
STARTED STARTED
此外,为了实现不同zk业务之间的隔离,会给每个业务分配一个独立的命名空间,即指定一个根路径,可以在builder时候进行namespace的指定即可。
创建&删除节点
api
节点的创建时候需要指定的属性有节点类型,节点权限,节点数据,节点path,同样curator使用fluent的方式进行创建, 如果节点已经存在,是不会报节点存在的错的,常见函数如下:
public CreateBuilder create(); // client调用create方法得到Builder
// 十分好用的递归创建,父节点节点属性默认是PERSISTENT
public ACLCreateModePathAndBytesable<String> creatingParentsIfNeeded();
public ACLBackgroundPathAndBytesable<String> withMode(CreateMode mode)
public String forPath(String path) throws Exception
public String forPath(final String givenPath, byte[] data) throws Exception
同样节点的删除也是遵循fluent风格:
public DeleteBuilder delete();
public ChildrenDeletable guaranteed();
// 递归删除所有子节点
public BackgroundVersionable deletingChildrenIfNeeded();
// 指定要删除的version
public BackgroundPathable<Void> withVersion(int version);
// 要删除的path
public Void forPath(String path) throws Exception;
实战
-
程序实例
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 CuratorZKCreateAndDeleteNode { public static void main(String[] args) throws Exception { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 2); // fluent风格创建client CuratorFramework fluentClient = CuratorFrameworkFactory .builder() .connectString("localhost:2181") .sessionTimeoutMs(5000) .retryPolicy(retryPolicy) .build(); fluentClient.start(); // fluent风格创建节点,返回只是节点path String result = fluentClient.create(). withMode(CreateMode.EPHEMERAL). forPath("/zk-test", "init".getBytes()); System.out.println(result); // 删除 fluentClient.delete(). guaranteed(). withVersion(0). forPath("/zk-test"); Thread.sleep(Integer.MAX_VALUE); } }
-
运行结果
/zk-test
获取&更新节点数据
api
Curator同样使用fluent风格进行节点数据获取:
public GetDataBuilder getData();
// 读取节点内容并且获取该节点的stat
public WatchPathable<byte[]> storingStatIn(Stat stat);
public byte[] forPath(String path) throws Exception;
设置节点数据方式:
public SetDataBuilder setData();
// 在某个版本上设置
public BackgroundPathAndBytesable<Stat> withVersion(int version)
public Stat forPath(String path, byte[] data) throws Exception
public Stat forPath(String path) throws Exception
实战
-
程序实例
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 CuratorZKGetAndUpdateDate { public static void main(String[] args) throws Exception { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 2); // fluent风格创建client CuratorFramework fluentClient = CuratorFrameworkFactory .builder() .connectString("localhost:2181") .sessionTimeoutMs(5000) .retryPolicy(retryPolicy) .build(); fluentClient.start(); // fluent风格创建节点,返回只是节点path String result = fluentClient.create(). withMode(CreateMode.EPHEMERAL). forPath("/zk-test", "init".getBytes()); System.out.println(result); // get data Stat stat = new Stat(); String data = new String(fluentClient. getData(). storingStatIn(stat). forPath("/zk-test")); System.out.println("data:" + data + ";version=" + stat.getVersion()); // set data fluentClient.setData(). withVersion(stat.getVersion()). forPath("/zk-test", "update".getBytes()); System.out.println(new String(fluentClient.getData().forPath("/zk-test"))); fluentClient.setData(). withVersion(stat.getVersion()). forPath("/zk-test", "update-again".getBytes()); Thread.sleep(Integer.MAX_VALUE); } }
-
运行结果
程序先通过获取data接口获取version信息,根据version信息更新结果,从新获取,这时候version已经改变,如果再次使用上一次的version进行更新数据就会报错:BadVersion
zk-test data:init;version=0 update Exception in thread "main" org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /zk-test
总结
本文从实战角度介绍了Curator操作zk的各种方法,可以看出Curator在原生api基础上提供了fluent风格的各种接口,使用更加方便,接下来我会使用Curator来实现几种zk常见的应用场景: Master选举,分布式锁,分布式队列等,尽情期待吧~
参考
- 从Paxo到Zookeeper: 分布式一致性原理与实战