五 、开源客户端(ZkClient + Curator)
1.ZkClient
ZkClient是由Datameer的工程师开发的开源客户端,对Zookeeper的原生API进行了包装,实现了超时重连、Watcher反复注册等功能。
想要使用ZkClient必须相应的jar包,由于我的工程使用maven构建的,故在此首先要引入maven依赖:
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
a.连接
ZkClient(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer);
//
zkServers: 连接字符串
sessionTimeOut: 会话超时时间
connectionTimeout: 连接超时时间
ZkSerializer: 自定义序列化器,使得zookeeper将设置节点内容为java对象。(原生仅支持byte[])
b.创建节点
ZkClient提供了较多的创建节点的API接口,且很多都跟原生API类似,但是有一个参数createParent比较有趣
void createPersistent(String path, boolean createParents);
//createParents: 是否递归创建父节点
c.删除节点
zookeeper原生API只允许删除叶子节点, ZkClient提供了一个新方法:deleteRecursive(),这个方法帮助我们逐层遍历删除叶子节点。
boolean deleteRecursive(String path)
d.读取数据(getChildren)
ZkClient只对外提供一个getChildren的API,而且该API没有了Watcher的功能。ZkClient提供了subscribeChildChanges方法来监听子节点的变更事件。
List<String> subscribeChildChanges(String path, IZkChildListener listener) ;
IZkChildListener主要可以监听到如下一下四种事件:
* path节点的创建(即可以监听一个不存在的节点,待该节点创建时,触发此事件)
* path节点的删除
* path新增子节点
* path删除子节点
需实现IZkChildListener并重写handleChildChange方法,注意此方法跟原生的Watcher不同,此方法是注册一次一直生效。
void handleChildChange(String parentPath, List<String> currentChilds);
e.读取数据(readData)
T readData(String path);
T readData(String path, boolean returnNullIfPathNotExists); //如果节点不存在返回null
T readData(String path, Stat stat);
ZkClient通过subscribeDataChanges来监听节点内容的变化:
subscribeDataChanges(String path, IZkDataListener listener)
需实现IZkDataListener 并重写handleDataChange和handleDataDelete方法
handleDataChange监听到:
* 该节点初次创建(即可以创建一个不存在的节点)
* 节点数据的变化
handleDataDelete可以监听到:节点的删除。
f.更新数据(writeData)
void writeData(final String path, Object datat, final int expectedVersion)
g.检测节点是否存在(exists)
boolean exists(final String path)
2.Curator
Curator是Netflix开源的一套ZooKeeper客户端框架.它解决了很多ZooKeeper客户端非常底层的操作如:连接重连,反复注册和NodeExistsExceptiion异常等,它目前已经成为了Apache的顶级项目。使用它,需要引入它的maven依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
a.创建会话
通过CuratorFrameworkFactory.newInstance(),创建CuratorFramework,最终调用CuratorFrameworkFactory.start()方法创建连接。
CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy)
其中,RetryPolicy为重试策略主要包含以下四种重试策略:
* ExponentialBackoffRetry :基于backoff的重连策略
ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
baseSleepTimeMs:初始sleep时间,
maxRetries: 最大重试次数
maxSleepMs:最大sleep时间
当前sleep时间 = baseSleepTimeMs * Math.max(1,random.nextInt( 1 << (retryCount + 1)));
所以sleep时间会越来越长,当这个时间大于maxSleepMs时,则取maxSleepMs
* RetryNTimes(int n, int sleepMsBetweenRetries): 重连N次策略
* RetryOneTime(int sleepMsBetweenRetry):重连一次
* RetryUntilElapsed(int maxElapsedTimeMs, int sleepMsBetweenRetries):以sleepMsBetweenRetries的间隔重连,直到超过maxElapsedTimeMs的时间设置
* RetryForever:永远重试策略
通过两种方式创建会话:
//1.工厂类创建
CuratorFramework client = CuratorFrameworkFactory.newClient(IZooKeeperConfig.CONNECT_STRING,
5000,
5000,
retryPolicy);
client.start();
//2.Fluent风格
client = CuratorFrameworkFactory.builder()
.connectString(IZooKeeperConfig.CONNECT_STRING)
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.namespace("base") //创建base节点
.build();
client.start();
同时支持namespace属性来对各个不同的业务进行隔离,若使用namespace属性,则再在该客户端上的操作节点都是基于namespace为相对目录进行的。
b.创建节点
使用create().forPath(path)创建一个空节点,额外属性的使用creatingParentsIfNeeded递归创建父节点,withMode指定模式;
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/book/ch5","haha".getBytes());
c.删除节点
使用delete().forPath(path)删除一个节点,额外属性的使用deletingChildrenIfNeeded递归删除子节点。
client.delete()
.deletingChildrenIfNeeded()
.withVersion(-1)
.forPath("/book/ch5");
d.读取数据
client.getData()
.storingStatIn(new Stat() )
.forPath("/book/ch5");
e.设置数据
client.setData()
.withVersion(stat.getVersion())
.forPath("/book/ch5", "adadfafa".getBytes());
f.异步接口
之前所介绍的api都是同步接口,Curator引入了BackgroundCallback接口,来处理异步调用服务端返回的的结果信息。BackgroundCallback只有一个processResult方法:
void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent);
CuratorEvent的事件类型主要有GET_DATA,SET_DATA,CHILDREN,SYNC,GET_ACL,WATCHED,CLOSING类型。
响应码:
0: ok
-4: connectionloss
-110: node not exists
-112: sessionexpired
Curator提供了如下API来支持异步操作:
public T inBackground(BackgroundCallback callback);
public T inBackground(BackgroundCallback callback, Object context);
public T inBackground(BackgroundCallback callback, Executor executor);
public T inBackground(BackgroundCallback callback, Object context, Executor executor);
重点关注下executor,zookeeper的所有异步操作都是由EventThread这个线程来处理的,当碰上一个复杂的处理单元,就会消耗过长的处理时间, 就会影响到后面的时间的处理。所以在此处允许用户传递一个Executor实例,可以把一些复杂的处理时间专门放到一个线程池中。
client.setData().withVersion(stat.getVersion())
.inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
}
}, Executors.newFixedThreadPool(2)) //使用异步方式获取数据
.forPath("/book/ch5", "adadfafa".getBytes());
g.NodeCache和NodeCacheListener
1).使用样例
NodeCache cache = new NodeCache(client, "/chap05", false);
cache.start(true);
cache.getListenable().addListener(()->{
System.out.println("变化了::"+cache.getCurrentData().getData());
});
2).NodeCache
想要使用必须先调用start方法,不用之后调用close方法.
NodeCache(CuratorFramework client, String path, boolean dataIsCompressed);
void start(boolean buildInitial); //若为true ,nodecache第一次启动时就从zookeeper上读取对应节点数据,并保存到cache中;
3).NodeCacheListener
cache.getListenable().addListener(XXNodeCacheListener);//使用此种方式,注册监听
需要实现NodeCacheListener 并重写nodeChanged()方法,
cache.getCurrentData().getData();//获取变更后的数据
可以监听到事件:
* 节点创建(即可以监听一个不存在的节点)
* 节点数据变化
h.PathChildrenCache和PathChildrenCacheListener
1).使用样例
PathChildrenCache childrenCache = new PathChildrenCache(client, "/chap05", true);
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
childrenCache.getListenable().addListener((CuratorFramework cc, PathChildrenCacheEvent event) -> {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("新增子节点:"+event.getData().getPath());
break;
case CHILD_REMOVED:
System.out.println("删除子节点:"+event.getData().getPath());
break;
case CHILD_UPDATED:
System.out.println("更新子节点:"+event.getData().getPath());
break;
default:
System.out.println(event.getType().toString()+":::::"+event.getData().getPath());
}
});
2).PathChildrenCache
想要使用必须先调用start方法,不用之后调用close方法.
start有两个, 其中一个可以传入StartMode,用来为初始的cache设置暖场方式(warm):
PathChildrenCache(CuratorFramework client, String path, boolean cacheData);
void start(StartMode mode);
NORMAL: 初始时为空。
BUILD_INITIAL_CACHE: 在这个方法返回之前调用rebuild()。
POST_INITIALIZED_EVENT: 当Cache初始化数据后发送一个
3).PathChildrenCacheListener
cache.getListenable().addListener(XXPathChildrenCacheListener);//使用此种方式,注册监听
实现PathChildrenCacheListener重写childEvent方法:
void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
可以监听到:
* 子节点新增、删除
* 子节点数据变更(不监听子节点添加or删除孙子节点)
3.Curator的典型应用场景
a.master选举-LeaderSelector
b.分布式锁–InterProcessMutex
void acquire(); //获得锁
void release();//释放锁
c.分布式计数器–DistributedAtomicInteger
d.分布式Barrier —DistributedBarrier
e.ZKPaths 和 EnsurePath