Curator是一套由netflix公司开源的,Java语言编程的zookeeper客户端框架,Curator项目是现在Zookeeper客户端使用最多的,对Zookeeper版本支持最好的第三方客户端,并推荐使用,Curator把我们平常常用的很多Zookeeper服务开发功能做了封装,例如Leader选举、分布式计数器、分布式锁。这就减少了技术人员在使用zookeeper时的大部分底层细节开发工作。在会话重新连接、Watch反复注册时,由于对其这些功能都做了高度的封装,使用起来更加简单,不但减少了开发时间,而且增强了程序的可靠性。
这是我们以maven工程为例,首先要引入Curator框架相关的开发包,这里为了方便测试引入了junit,lombok,由于Zookeeper本身以来了log4j日志框架,所以这里可以创建对应的log4j配置文件后直接使用。如下面的代码所示,我们通过将Curator相关的引用包配置到Maven工程的pom文件中,将Curator框架引用到工程项目里,在配置文件中分别应用里两个Curator相关的包,第一个是curator-framework包,该包是对zookeeper底层API的一些封装。另一个是curator-recipes包,该包封装了一些Zookeeper服务的高级特性,如:Cache时间监听、选举、分布式锁、分布式barrier。
<dependency>
<groupId>org.apache.curator</groupId
<artifactId>curator-recipes</artifactId>
<version>5.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
创建会话
使用fluent风格创建
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("localhost:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000)
.retryPolicy(retryPolicy).build();
client.start();
这段代码的编码风格采用了流式编程方式,最核心的是CuratorFramework类,该类的作用是定义一个zookeeper客户端对象,并在之后的上下文中使用。在定义CuratorFramework对象实例的时候,我们使用CuratorFramework类,该类的作用是定义一个zookeeper客户端对象,并在之后的上下文中使用。在定义CuratorFramework对象实例的时候,我们使用了CuratorFrameworkFactory工厂方法,并指定了connectiongString 服务器地址列表、retryPolicy重试策略、sessionTimeoutMs会话超时时间。下面我们分别对这几个参数进行详解。
connectingString:服务器地址列表,在指定服务器地址列表的时候可以是一个地址,可以是多个地址。如果是多个地址,那么每个服务器地址列表用逗号分隔,如host1:port1,host2:port2,host3:port3
retryPolicy:重试策略,当客户端异常退出或者与服务端失去连接时候,可以通过设置客户端重新连接Zookeeper服务端。而Curator提供了一次重试、多次重试等不同种类的实现方式。在Curator内部,可以通过判断服务器返回keeperException的状态码来判断是否进行重试处理,如果返回的是OK表示一切操作都没有问题,而SYSTEMERROR表示系统或服务端错误。
ExponentialBackoffRetry:重试一组次数,重试之间的睡眠时间增加
RetryNTimes:重试最大次数
RetryOneTime:只重试一次
RetryUnitElapsed:在指定的时间结束之前重试
超时时间:Curator客户端创建过程中,有两个超时时间的设置。一个是sessionTimeoutMs会话超时时间,用来设置该条会话在Zookeeper服务端的失效时间。另一个是connectionTimeoutMs 客户端创建会话的超时时间,用来限制客户端发起一个会话连接到接收Zookeeper服务端应答的时间。SessionTimeoutMs作用在服务端,而connectionTimeoutMs作用在客户端
创建节点
private static void createNode() throws Exception {
String path = initZookeeperClient().create().forPath("/curator-node111");
log.info("curator create node:{} successfully",path);
}
在Curator中,可以使用create函数创建数据节点,并通过withMode函数指定节点类型(持久化节点,临时节点,顺序节点,顺序临时节点,持久化顺序节点等),默认是持久化节点,之后调用forPath函数来指定节点的路径和数据信息。
一次性创建带层级结构的节点
private static void createNodeLevel() throws Exception {
String path = initZookeeperClient().create().creatingParentsIfNeeded().forPath("/node-parent/sub-ndoe1");
log.info("curator create node:{} successfully",path);
}
获取数据
private static void getNode() throws Exception {
byte[] bytes = initZookeeperClient().getData().forPath("/curator-node");
log.info("get data from node: {} successfully",new String(bytes));
}
更新节点
我们通过客户端实例的setData()方法更新Zookeeper服务上的数据节点,在setData方法的后边,通过forPath函数指定更新的数据节点路径以及要更新的数据。
private static void updateNode() throws Exception {
initZookeeperClient().setData().forPath("/curator-node","changed".getBytes());
byte[] by = initZookeeperClient().getData().forPath("/curator-node");
log.info("get data:{}",new String(by));
}
删除节点
private static void deleteNode() throws Exception {
initZookeeperClient().delete().guaranteed().deletingChildrenIfNeeded().forPath("/curator-node");
}
guaranteed:该函数的功能如字面意思一样,主要起到一个保障删除成功的作用,其底层工作的方式:只要客户端的会话有效,就会在后台持续发起删除请求,直到该数据节点在Zookeeper服务端被删除。
deletingChildIfNeeded:指定了该函数后,系统在删除该数据节点的时候会以递归的方式直接删除其子节点,以及节点的子节点。
异步接口
Curator引入了BackgroundCallback接口,用来处理服务端返回来的信息,这个处理过程是在异步线程中调用,默认在EventThread中调用,可以以自定义线程池。指定
private static void asyncGetData() throws Exception {
initZookeeperClient().getData().inBackground((item1,item2) -> {
log.info("background item1:{} ",item1);
log.info("background itme2:{} " ,new String(item2.getData()));
}).forPath("/curator-node");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
指定线程池
private static void threadPool() throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
initZookeeperClient().getData().inBackground((item1,item2) -> {
log.info("background:{}",new String(item2.getData()));
System.out.println("dfdffdsfsf");
},executorService).forPath("/curator-node");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
Curator监听器
针对background通知和错误通知。使用此监听器之后,调用inBackground方法会异步获得监听。
Curator 引入了Cache来实现对Zookeeper服务端事件进行监听,Cache事件监听可以理解为一个本地缓存缓存视图与远程Zookeeper视图的对比过程。Cache提供了反复注册的功能。Cache分别为两类注册类型:节点监听和子节点监听。
NodeCache对某一个节点进行监听
private static void monitorNode() throws Exception {
NodeCache nodeCache = new NodeCache(initZookeeperClient(), "/curator-node111");
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{},path nodecached","/curator-node");
}
});
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
nodeCache.start(true);
}
pathCache
PathChildrenCache 会对子节点进行监听,但是不会对二级子节点进行监听
PathChildrenCache pathChildrenCache = new PathChildrenCache(initZookeeperClient(), "/path‐cache", true);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
log.info("event:{}",pathChildrenCacheEvent);
}
});
pathChildrenCache.start(true);
tree cache:
TreeCache 使用一个内部类TreeNode来维护这个一个树结构。并将这个树结构与ZK节点进行了映射。所以TreeCache可以监听当前节点下的所有节点的事件
TreeCache treeCache = new TreeCache(initZookeeperClient(), "/tree-path");
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
log.info("tree cache:{}",treeCacheEvent.toString());
}
});
treeCache.start();
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);