Curator是Netflix公司开源的一套ZooKeeper客户端框架,作者是Jordan Zimmerman。和ZkClient一样,Curator解决了很多ZooKeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等,目前已经成为了Apache的顶级项目。Patrick Hunt(ZooKeeper代码的核心提交者)以一句“Guava is to Java what Curator is to ZooKeeper”对其进行了高度评价。
除此之外,Curator中还提供了ZooKeeper各种应用场景(Recipe,如共享锁服务、Master选举几种和分布式计数器等)的抽象封装。
首先看一下Curator的Maven依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.5.0</version>
</dependency>
这里我们加入依赖curator-recipes,因为它也包含了curator-framework:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.5.0</version>
</dependency>
创建会话
1.使用CuratorFrameworkFactory这个工厂类的两个静态方法来创建一个客户端:
public static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy)
public static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy)
也可以自己调用Fluent风格的API来创建:
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
效果是一样的,因为第一种方式的内部也是调用了第二种的方法。
2.通过CuratorFramework中的start()方法来启动会话。
Curator会话创建API参数说明
参数名 | 说明 |
connectString | 指Zk服务器列表,由英文状态逗号分开的host:port字符串组成,每一个都代表一台zk机器,如,192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181 |
retryPolicy | 重试策略。默认主要有四种实现,分别是Exponential BackoffRetry、RetryNTimes、RetryOneTime、RetryUntilElapsed |
sessionTimeoutMs | 会话超时时间,单位为毫秒。默认是60000ms |
connectionTimeoutMs | 连接创建超时时间,单位为毫秒。默认是15000ms |
在重试策略上,Curator通过一个接口RetryPolicy来让用户实现自定义的重试策略。在RetryPolicy接口中只定义了一个方法:
boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper);
RetryPolicy接口参数说明:
参数名 | 说明 |
retryCount | 已经重试的次数。如果是第一次重试,那么该参数为0 |
elapsedTimeMs | 从第一次重试开始已经花费的时间,单位为毫秒 |
sleeper | 用于sleep指定时间。Curator建议不要使用Thread.sleep来进行sleep操作 |
使用Curator创建会话:
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",
5000, 3000, new ExponentialBackoffRetry(1000, 3));
client.start();
该示例中,我们创建了一个名为ExponentialBackoffRetry的重新策略,该重试策略的构造方法如下:
ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries)
ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
构造方法参数说明:
参数名 | 说明 |
baseSleepTimeMs | 初始sleep时间 |
maxRetries | 最大重试次数 |
maxSleepMs | 最大sleep时间 |
使用Fluent风格的API接口来创建会话:
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("47.96.164.224:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
创建节点
//创建一个节点,初始内容为空。注意:若没有设置节点属性,则Curator默认创建的是持久节点,内容默认是空。
client.create().forPath(String path);
//创建一个节点,附带初始内容。
client.create().forPath(String path, byte[] data);
//创建一个临时节点,初始内容为空
client.create().withMode(CreateMode.EPHEMERAL).forPath(String path);
//创建一个临时节点,并自动递归创建父节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL)
.forPath(String path);
使用Curator创建节点:
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("47.96.164.224:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
System.out.println("连接成功===============");
//创建节点
try {
String result = client.create().creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath("/curator/curator1/curator1-1", "123".getBytes());
System.out.println("result========="+result);
} catch (Exception e1) {
e1.printStackTrace();
}
删除节点
//删除一个节点,注意:使用该接口,只能删除叶子节点
client.delete().forPath(String path);
//删除一个节点,并递归删除其所有子节点
client.delete().deletingChildrenIfNeeded().forPath(String path);
//删除一个节点,强制指定版本进行删除
client.delete().withVersion(int version).forPath(String path);
//删除一个节点,强制保证删除,注意:guaranteed()接口是一个保障措施,只要客户端会话有效,
//那么Curator会在后台持续进行删除操作,直到节点删除成功。
client.delete().guaranteed().forPath(String path);
删除节点示例:
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("47.96.164.224:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
System.out.println("连接成功===============");
//删除节点
try {
client.delete().deletingChildrenIfNeeded().forPath("/curator");
} catch (Exception e) {
e.printStackTrace();
}
在ZooKeeper客户端使用过程中,可能会碰到这样的问题:客户端执行一个删除节点操作,但由于一些网络愿意,导致删除失败。对于这个异常,在有些场景中是致命的,如“Master选举”。针对这个问题,Curator引入了一种重试几种:guaranteed()方法。
读取数据
//读取一个节点的数据内容
client.getData().forPath(String path);
//读取一个节点的数据内容,同时获取到该节点的stat。Curator通过传入一个旧的stat变量的方式来存储服务端
//返回的最新的节点状态信息
client.getData().storingStatIn(stat).forPath(String path);
示例:
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("47.96.164.224:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
System.out.println("连接成功===============");
Stat stat = new Stat();
try {
//查询数据
byte[] b = client.getData().storingStatIn(stat)
.forPath("/curator/curator1/curator1-1");
System.out.println(new String(b)+"-->"+stat);
} catch (Exception e) {
e.printStackTrace();
}
更新数据
//更新一个节点的数据内容。调用该接口后,会返回一个Stat对象。
client.setData().forPath(String path);
//更新一个节点的数据内容,强制指定版本进行更新。注意:withVersion接口就是用来实现CAS的,
//version通常是从一个旧的stat对象中获取到的。
client.setData().withVersion(int version).forPath(String path);
示例:
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("47.96.164.224:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
System.out.println("连接成功===============");
try {
//修改数据
client.setData().forPath("/curator/curator1/curator1-1", "Hello".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
异步接口
Curator中引入了BackgroundCallback接口,用来处理异步接口调用之后服务端返回的结果信息。其接口定义如下:
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;
}
BackgroundCallback接口中的processResult方法的参数说明:
参数名 | 说明 |
client | 当前客户端实例 |
event | 服务端事件 |
Curator异步化API:
public interface Backgroundable<T>
{
/**
* Perform the action in the background
*
* @return this
*/
public T inBackground();
/**
* Perform the action in the background
*
* @param context context object - will be available from the event sent to the listener
* @return this
*/
public T inBackground(Object context);
/**
* Perform the action in the background
*
* @param callback a functor that will get called when the operation has completed
* @return this
*/
public T inBackground(BackgroundCallback callback);
/**
* Perform the action in the background
*
* @param callback a functor that will get called when the operation has completed
* @param context context object - will be available from the event sent to the listener
* @return this
*/
public T inBackground(BackgroundCallback callback, Object context);
/**
* Perform the action in the background
*
* @param callback a functor that will get called when the operation has completed
* @param executor executor to use for the background call
* @return this
*/
public T inBackground(BackgroundCallback callback, Executor executor);
/**
* Perform the action in the background
*
* @param callback a functor that will get called when the operation has completed
* @param context context object - will be available from the event sent to the listener
* @param executor executor to use for the background call
* @return this
*/
public T inBackground(BackgroundCallback callback, Object context, Executor executor);
}
在Zk中,所有异步通知事件处理都是由EventThread这个线程来处理的——EventThread线程用于串行处理所有的事件通知。EventThread的“串行处理机制”在绝大部分应用场景下能够保证对事件处理的顺序性,但这个特性也有其弊端,就是一旦碰上一个复杂的处理单元,就会消耗过长的处理时间,从而影响对其他事件的处理。因此在inBackground接口中,允许用户传入一个Executor实例,这样就可以把那些比较复杂的事件处理放到一个专门的线程池中去,如Executors.newFixedThreadPool(2)。
异步示例:
ExecutorService service = Executors.newFixedThreadPool(1);
final CountDownLatch latch = new CountDownLatch(1);
try {
//异步操作
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).
inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent event) throws Exception {
System.out.println(Thread.currentThread().getName()+"-->resultCode:"+event.getResultCode()+
"-->type:"+event.getType());
latch.countDown();
}
}, service).forPath("/curator", "张三".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
latch.await();
service.shutdown();
事务处理
事务操作是Curator独有的,一组crud操作同生同灭。如下:
try {
//事务操作(curator独有的)
Collection<CuratorTransactionResult> results = client.inTransaction().create()
.forPath("/curator", "123".getBytes()).and()
.setData().forPath("/zkclient", "234".getBytes()).and().commit();
for(CuratorTransactionResult result : results) {
System.out.println(result.getForPath()+"-->"+result.getType());
}
} catch (Exception e) {
e.printStackTrace();
}
and()前后的操作是在同一个事务中,要么都成功,要么都失败。
————————————————————————————————————————————————————