创建会话
使用curator客户端创建会话和其它客户端产品有很大不同
1.使用CuratorFrameworkFactory这个工厂类的两个静态方法来创建一个客户端:
public static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy)
public static CuratorFramework newClient(String connectString, int sessionTimeoutMs,
int connectionTimeoutMs, RetryPolicy retryPolicy)
2.通过调用CuratorFramework的start()方法来启动会话
在重试策略上,Curator通过一个接口来让用户实现自定义的重试策略。在RetryPolicy接口中之定义了一个方法:
public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper);
使用Curator创建会话
package session;
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) throws InterruptedException {
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.64.60:2181", retry);
client.start();
Thread.sleep(Integer.MAX_VALUE);
}
}
我们创建了一个名为ExponentialBackoffRetry的重试策略,该重试策略是Curator默认提供的几种重试策略之一,其构造方法如下:
public ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries)
public ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
使用Fluent风格的API接口来创建会话
Curator提供的API接口在设计上最大的亮点就是其遵守了Fluent设计风格。
package session;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class FluentSession {
public static void main(String[] args) {
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.64.60:2181").
sessionTimeoutMs(3000).retryPolicy(retry).build();
client.start();
}
}
使用Curator创建含隔离命名空间的会话
为了实现不同的zookeeper业务之间的隔离,往往会为每个业务分配一个独立的命名空间,即指定一个zookeeper根路径。下面
所示的独立命名空间为/base,那么该客户端对zookeeper上数据节点的任何操作,都是基于对该相对目录进行的:
创建节点
Curator中提供了一系列的Fluent风格的接口,开发人员可以通过对其进行自由组合来完成各种类型节点的创建。
创建一个节点,初始内容为空
client.create().forPath(path)
创建一个节点附带初始内容
client.create().forPath(path,"init".getBytes())
如果没有设置节点属性,那么Curator默认创建的是持久节点,内容默认是空
client.create().withMode(CreateMode.EPHEMERAL).forPath(path)
如果需要递归创建
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path)
删除节点
client.delete().forPath(path);
client.delete().deletingChildrenIfNeeded().forPath(path);
client.delete().withVersion(1).forPath(path)
client.delete().guaranteed().forPath(path)
guaranteed接口是一个保障措施,只要客户端会话有效,那么Curator会在后台持续进行删除操作
直到节点删除成功。
读取数据
client.getData().forPath(path);
client.getData().storingStatIn(stat).forPath(path);
通过传入一个旧的stat来获取旧的节点信息。
更新数据
client.setData().forPath(path);
//调用该接口后,会返回一个stat对象。
client.setData().withVersion(version).forPath(path)
version是用来实现CAS的,version信息通常事重一个旧的stat对象中获取到
异步接口
curator中引入了BackGroudCallBack接口,用来处理异步接口调用接口调用之后服务端返回的结果信息。
BackgroundCallback接口中只有一个processResult方法。从注释中可以看出来方法会在操作完成后异步调用
CuratorEvent这个参数定义了zookeeper服务端发送到客户端的一系列事件参数。其中比较重要的有事件类型和响应码来两个参数。
事件类型(curatorEventType)
响应码(int)
在这些API接口中,我们重点看一下executor参数。在zookeeper中,所有的异步通知事件处理都是由
EventThread这个线程来处理的——EventThread线程用于串行化处理所有的事件通知。EventThread的“串行化处理机制”
在绝大部分情况下能保证事件处理的顺序性,这个特性也有一个弊端,就是一旦碰上复杂的处理单元,会消耗长时间处理
,所以允许用户传入一个Executor实例,这样一来就可以把哪些比较复杂的事件处理放到一个专门的线程池中去
package async;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class AsyncCuStor {
static String path ="/zk-book";
static CuratorFramework client = CuratorFrameworkFactory.builder().
connectString("192.168.64.60:2181").sessionTimeoutMs(5000).
retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
static ExecutorService tp = Executors.newFixedThreadPool(2);
public static void main(String[] args) throws Exception {
client.start();
System.out.println("main thread"+Thread.currentThread().getName());
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).
inBackground(new BackgroundCallback() {
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
// TODO Auto-generated method stub
System.out.println(event.getResultCode()+"---"+event.getType());
System.out.println(Thread.currentThread().getName());
}
}, "", tp).forPath(path);
Thread.sleep(Integer.MAX_VALUE);
}
}
典型使用场景
1.事件监听
zookeeper原生支持通过Watcher来进行事件监听,但是其使用并不是特别方便,需要开发人员自己反复注册watcher。curator
引入了Cache来实现对zookeeper服务端事件的监听。curator是对zookeeper中事件监听的包装,其对事件监听可以看做是一个本地缓存视图和服务器视图的一个比对。
同事curator能够解决反复注册监听的繁琐。
Cache分为两类监听:一种是节点监听,一种是子节点监听。
NodeCache
NodeCache用于监听指定zookeeper数据节点本身的变化
NodeCache(CuratorFramework client, String path)
public NodeCache(CuratorFramework client, String path, boolean dataIsCompressed)
同时,NodeCache定义了事件处理的回调接口NodeCacheListener。
当数据节点的内容发生变化的时候,就会回调该方法。
package cache;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class NodeCache_Sample {
static String path ="/zk-book/nodecache";
static CuratorFramework client = CuratorFrameworkFactory.
builder().connectString("192.168.64.60:2181").connectionTimeoutMs(5000).
retryPolicy(new ExponentialBackoffRetry(3000, 3)).build();
public static void main(String[] args) throws Exception {
client.start();
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(path,"init".getBytes());
final NodeCache cache = new NodeCache(client, path,false);
cache.start(true);
cache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() throws Exception {
System.out.println("node data update, new data"+
new String(cache.getCurrentData().getData()));
}
});
client.setData().forPath(path,"u".getBytes());
Thread.sleep(5000);
client.delete().deletingChildrenIfNeeded().forPath(path);
Thread.sleep(Integer.MAX_VALUE);
}
//调用cache的start的方法,方法有个boolean类型的参数,默认为false
//如果设置为true,那么第一次启动就会从zookeeper上读取对应节点的数据
//内容,并保存在Cache中。
//NodeCache不仅可以监听数据节点内容的变更,也可以监听指定节点是否存在
//如果指定节点不存在那么就会触发NodeCacheListener,如果该节点被删除
//那么curator将无法触发NodeCacheListener。
}
PathChildrenCache
pathChildrenCache用于监听指定zookeeper数据节点的子节点变化情况。
PathChildrenCache定义了事件处理的回调接口PathChildrenCacheListener,其定义如下。
当指定节点的子节点发生变化时,就会回调该方法。
package cache;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class PathChildrenCache_sample {
static String path ="/zk-book/nodecache";
static CuratorFramework client = CuratorFrameworkFactory.
builder().connectString("192.168.64.60:2181").connectionTimeoutMs(5000).
retryPolicy(new ExponentialBackoffRetry(3000, 3)).build();
public static void main(String[] args) throws Exception {
client.start();
PathChildrenCache cache = new PathChildrenCache(client, path, true);
cache.start(StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
// TODO Auto-generated method stub
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("child add"+event.getData().getPath());
break;
case CHILD_UPDATED:
System.out.println("child update"+event.getData().getPath());
case CHILD_REMOVED:
System.out.println("child remove"+event.getData().getPath());
default:
break;
}
}
});
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(path,"init".getBytes());
client.setData().forPath(path,"u".getBytes());
Thread.sleep(5000);
client.delete().deletingChildrenIfNeeded().forPath(path);
Thread.sleep(Integer.MAX_VALUE);
}
}
Master
对于一个复杂的任务,仅需要从集群中选举一台进行处理即可。诸如此分类的分布式问题,我们统称“Master”选举。借助zookeeper,
我们可以很方便的实现选举功能。
大体思路:
选择一个根节点,例如/master_select,多台机器同时像该节点创建一个子节点/master_select/lock,利用zookeeper的特性,最终只有一台
机器能够创建成功,成功的那台机器就作为Master。
Curator也是基于这个思路,但是它将节点创建,事件监听和自动选举进行了封装。开发人员只需要调用简单的API即可实现
Master选举。
package master;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class PullMaster {
static String master_path = "/master_path";
static CuratorFramework client = CuratorFrameworkFactory.builder().
connectString("192.168.64.60:2181")
.connectionTimeoutMs(5000).
retryPolicy(new ExponentialBackoffRetry(3000, 3))
.build();
public static void main(String[] args) throws InterruptedException {
client.start();
LeaderSelector leaderSelector = new LeaderSelector(client, master_path, new LeaderSelectorListenerAdapter() {
public void takeLeadership(CuratorFramework client) throws Exception {
// TODO Auto-generated method stub
System.out.println("成为Master角色");
Thread.sleep(3000);
System.out.println("完成Master操作,释放Master权利");
}
});
leaderSelector.autoRequeue();
leaderSelector.start();
Thread.sleep(Integer.MAX_VALUE);
}
}
可以看到主要是创建了一个LeaderSelector实例,该实例负责封装所有和Master选举相关的逻辑,
包含和zookeeper服务器交互的过程。其中master_path代表了一个Master选举的根节点,表明本次Master选举都是在
该节点下进行的。
在创建LeaderSelector实例的时候,还会传入一个监听器:leaderSelectorListenerAdapter。这需要开发人员自行去实现。
curator会在成功获取Master权利的时候回调该监听器。
一旦执行完takeLeadership方法,curator就会立即释放Master权利,然后重新开始新一轮的Master选举。