上一篇文章使用了zookeeper原生包进行操作zk服务器,本篇使用Curator进行操作,Curator是NetFlix公司开源的一套Zookeeper的客户端框架,使用此框架,可以省去许多底层需要处理的细节。
一、使用Curator客户端
使用前我们从maven仓库中找到Curator的依赖包,只需要引入一个包即可
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.7.1</version>
</dependency>
1.创建会话
使用CuratorFrameworkFactory进行创建客户端连接对象。RetryPolicy为重试策略,默认有4种重试策略ExponentialBackOffRetry,RetryNTimes,RetryOneTime,RetryUntilElapsed
RetryPolicy接口中存在一个方法:
var1:已经重试次数
var2: 从第一次重试开始已经花费的时间
var4: 用于sleep指定时间
boolean allowRetry(int var1, long var2, RetrySleeper var4);
示例代码:
public CuratorFramework connectionZkWithFluent(){
/**
* 创建ExponentialBackoffRetry重试策略
* baseSleepTimeMs:初始sleep时间
* maxRetries:最大重试次数
*/
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
//通过工厂进行创建client
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString(host+":"+port)
.sessionTimeoutMs(60000)
.retryPolicy(retryPolicy)
//为不同项目指定命名空间
.namespace("base")
.build();
curatorFramework.start();
System.out.println("启动连接成功");
return curatorFramework;
}
2.创建节点
直接使用client进行创建,使用forPath进行规定节点位置
public void createNode() throws Exception {
CuratorFramework client = connectionZkWithFluent();
//创建一个不带数据的节点
String node1 = client.create().forPath("/node1");
System.out.println("创建节点Node1:"+node1);
//创建一个带数据的节点
String node2 = client.create().forPath("/node2","node2data".getBytes());
System.out.println("创建节点Node2:"+node2);
//创建一个带有节点类型的节点
String node3 = client.create().withMode(CreateMode.PERSISTENT).forPath("/node3");
System.out.println("创建节点Node3:"+node3);
//创建一个自动补充父级的节点creatingParentsIfNeeded
String node4 = client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node4/node4-1/node4-2");
System.out.println("创建节点Node4:"+node4);
}
3.删除节点
直接使用client进行调用,使用forPath进行规定节点位置
public void deleteNode() throws Exception {
CuratorFramework client = connectionZkWithFluent();
//只能删除叶子节点
client.delete().forPath("/node1");
//递归循环删除节点,删除节点下所有节点
client.delete().deletingChildrenIfNeeded().forPath("/node4");
//删除指定version的节点
client.delete().withVersion(0).forPath("/node2");
//在会话有效期内,后台会一直进行删除操作,直至删除成功.避免网络等原因没有调到或失败情况
client.delete().guaranteed().forPath("/node3");
}
4.获取数据
直接使用client进行调用,使用forPath进行规定节点位置
public void getData() throws Exception {
CuratorFramework client = connectionZkWithFluent();
//获取数据,返回字节数组
byte[] bytes = client.getData().forPath("/node1");
//查找数据,并且返回对应的stat
Stat stat = new Stat();
byte[] node2s = client.getData().storingStatIn(stat).forPath("node2");
}
5.更新数据
直接使用client进行调用,使用forPath进行规定节点位置
public void updateData() throws Exception {
CuratorFramework client = connectionZkWithFluent();
//更新
Stat stat = client.setData().forPath("/node1", "tom".getBytes());
//指定版本更新
Stat stat1 = client.setData().withVersion(0).forPath("node2", "jerry".getBytes());
}
6.异步调用
异步调用使用inBackground()并且传入回调处理的方法。并且支持传入单独的线程池用于处理回调方法,避免任务多时串行影响执行效率。
public void backgroud() throws Exception {
CuratorFramework client = connectionZkWithFluent();
ExecutorService pool = Executors.newFixedThreadPool(2);
String s = client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("code:"+curatorEvent.getResultCode() + "type:"+curatorEvent.getType());
System.out.println("thread:"+Thread.currentThread().getName());
}
//此处支持传入线程池处理当前callback方法,避免长时间处理造成影响
},pool).forPath("/node7");
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("code:"+curatorEvent.getResultCode() + "type:"+curatorEvent.getType());
System.out.println("thread:"+Thread.currentThread().getName());
}
}).forPath("/node");
}
二、常用场景使用
Curator不仅为开发者提供了便利的api,还提供一些复杂场景需要调用的接口,比如事件监听、Master选举、分布式锁、分布式计数器,都可以快速实现。这些接口在另外一个包中,我们可以通过maven中心仓库进行获取
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
1.事件监听
NodeCache
nodeCache用于监听ZK节点本身的变化。可以对不存在的节点进行监听,如果创建了节点,那么会进行通知处理;如果数据节点发生了变化也会通知;当节点被删除同样会被通知。同时使用提供的Cache是对原生Watcher的封装,无需手动反复注册watcher对象。
NodeCache对应的通知类是NodeCacheListener,需要实现listener进行监听。
示例代码:
/**
* 使用nodecache
*/
public void addWatcher() throws Exception {
CuratorFramework client = connectionZkWithFluent();
String path = client.create().creatingParentsIfNeeded().forPath("/watcher/watcher2");
final NodeCache nodeCache = new NodeCache(client,path);
nodeCache.start();
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
if(nodeCache.getCurrentData() == null){
System.out.println("已经删除了当前节点:"+path);
return;
}
System.out.println("监听内容nodechange:"+new String(nodeCache.getCurrentData().getData()));
}
});
client.setData().forPath(path,"test".getBytes());
Thread.sleep(3000);
client.delete().deletingChildrenIfNeeded().forPath(path);
Thread.sleep(5000);
String s = client.create().creatingParentsIfNeeded().forPath("/watcher/watcher2");
System.out.println("再次创建完成:"+s);
Thread.sleep(5000);
}
示例中connectionZkWithFluent()方法为初始化client参数,在上一篇文章有介绍。
PathChildrenCache
PathChildrenCache用于监听节点下子节点的变化,包括增加、删除、修改数据变化。
PathChildrenCache构造函数中参数cacheDate参数为是否缓存节点数据,当为true时候,回调数据时会得到变化节点的数据。
PathChildrenCache对应的监听器接口是PathChildrenCacheListener,实现此listener即可实现监听的任务处理,监听事件类型包括三种,新增子节点、变更子节点、删除子节点。
示例代码:
/**
* 使用pathChildrenCache
* @throws Exception
*/
public void pathChildrenCache() throws Exception {
CuratorFramework client = connectionZkWithFluent();
client.delete().deletingChildrenIfNeeded().forPath("/watcher/watcher3");
String path = client.create().creatingParentsIfNeeded().forPath("/watcher/watcher3");
PathChildrenCache cache = new PathChildrenCache(client,path,true);
cache.start();
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("类型:"+pathChildrenCacheEvent.getType()+" 数据:"+ new String(pathChildrenCacheEvent.getData().getData()));
}
});
client.create().creatingParentsIfNeeded().forPath("/watcher/watcher3/watcher1");
Thread.sleep(3000);
client.create().creatingParentsIfNeeded().forPath("/watcher/watcher3/watcher2");
Thread.sleep(3000);
client.setData().forPath("/watcher/watcher3/watcher2","valuetest".getBytes());
Thread.sleep(3000);
client.delete().deletingChildrenIfNeeded().forPath("/watcher/watcher3/watcher1");
Thread.sleep(3000);
}
注意: 此处监听子节点变化,仅支持当前节点的子节点,无法监听到子节点下级节点。无法对二级节点进行监听
2.Master选举
3.LeaderSelector
Curator中提供快速选举的能力,可以在集群环境中使用zk进行选举主服务进行操作数据。
原理:多个选举的master服务会抢着在path路径下进行创建节点,当创建成功后说明获取到master权限,选举成功,进行回调leaderSelectorListener进行通知,可以传入单独的线程池供回调方法使用。
使用提供的LeaderSelector进行选举。构造函数中包括:
client:当前客户端
path: 需要注册master的路径地址
leaderSelectorListener: 采用此监听器进行通知master服务,当前服务被选举成为master后,会调用takeLeadership方法进行处理。当takeLeadership方法执行完成后便失去master权利。
示例代码:
public void chooseMaster() throws InterruptedException {
CuratorFramework client = connectionZkWithFluent();
LeaderSelector selector = new LeaderSelector(client, "/master", new LeaderSelectorListener() {
@Override
public void takeLeadership(CuratorFramework curatorFramework) throws Exception {
System.out.println(Thread.currentThread().getName() + "成为leader");
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName() + "释放master");
}
@Override
public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {
// System.out.println("状态变更connectionState={}",connectionState.CONNECTED.isConnected());
System.out.println("状态变更" + connectionState.isConnected());
}
});
//开启后可以进行重新排队,进行获取master权限
selector.autoRequeue();
selector.start();
Thread.sleep(new Random().nextInt(10000000));
}
代码中使用selector.autoRequeue()方法后,在失去master权利之后,会重新再次自动进行master选举。再次被选择为Master后将再次进行takeLeadership方法执行。
真实场景中自动排队使用场景较少,并不希望一直在自动选master,而是当集群中其他机器新增或掉线时候发起重新选举,所以可以使用PathChildrenCache和LeaderSelector一起来完成使用。
3.分布式锁
InterProcessMutex
使用InterProcessMutex工具类进行获取锁和释放锁,调用acquire()进行获取锁,调用release()进行释放锁。
实例代码:
public void getLock(){
CuratorFramework client = connectionZkWithFluent();
InterProcessMutex lock = new InterProcessMutex(client,"/lock");
CountDownLatch count = new CountDownLatch(1);
for(int i = 0 ; i <20;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
count.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.acquire();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis());
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
count.countDown();
}
以上使用Curator包常用的工具类都已经写完,如果有需要使用的地方可以直接复制进行验证使用。在挖掘技术路上还需要继续深耕。