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()前后的操作是在同一个事务中,要么都成功,要么都失败。

————————————————————————————————————————————————————