Zookeeper开源客户端Curator的使用
开源zk客户端-Curator#
创建会话:
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
使用CuratorFrameworkFactory工厂类的静态方法newClient来创建会话。
在重试策略上, Curator通过一个接口RetryPolicy来让用户实现自定义的重试策略。在RetryPolicy接口中只定义了一个方法:
boolean allowRetry(int var1, long var2, RetrySleeper var4);
三个参数分别为:
- baseSleepTimeMs:初始sleep时间
- maxRetries:最大重试次数
- maxSleepMs:最大sleep时间
ExponentialBackoffRetry的重试策略设计如下:
给定一个初始sleep时间baseSleepTimeMs,在这个基础上结合重试次数,通过以下公式计算出当前需要sleep的时间:
当前sleep时间 = baseSleepTimeMs * Math.max(1,random.nextInt(1 << (retryCount = 1)))
可以看出,随着重试次数的增加,计算出的sleep时间会越来越大。如果该sleep时间在maxSleepMs的范围之内,那么就使用该sleep时间,否则使用maxSleepMs。另外,maxRetries参数控制了最大重试次数,以避免无限制的重试。
另外,newClient方法并没有完成创建客户端的工作,你需要主动调用CuratorFramework的start()方法来完成创建客户端。
一个完整的创建客户端的例子:
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CreateBuilder;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class Demo1 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
CreateBuilder builder = client.create();
try {
builder.withMode(CreateMode.EPHEMERAL).forPath("/test","ceshi".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建一个节点,初始内容为空:
builder.forPath("/test","ceshi".getBytes());
注意,如果没有设置节点属性,那么Curator默认创建的是持久节点,内容默认是空。
创建一个临时节点,初始内容为空:
builder.withMode(CreateMode.EPHEMERAL).forPath("/test");
创建一个临时节点,并自动逆归创建父节点:
builder.creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/test","ceshi".getBytes());
这个接口非常有用,在使用ZooKeeper 的过程中,开发人员经常会碰到
NoNodeException异常,其中一个可能的原因就是试图对一个不存在的父节点创建子节点。因此,开发人员不得不在每次创建节点之前,都判断-下该父节点是否存在。在使用Curator之后,通过调用creatingParentsIfNeeded接口,Curator就能够自动地递归创建所有需要的父节点。
同时要注意的一点是,由于在ZooKeeper中规定了所有非叶子节点必须为持久节点,调用上面这个API之后,只有path参数对应的数据节点是临时节点,其父节点均为持久节点。
删除节点:
同样创建和删除操作都是由CuratorFramework接口发出来的。
client.delete().forPath("/test/test1");
使用上面的方法只能删除叶子节点。
删除一个节点,并递归删除其所有子节点:
client.delete().deletingChildrenIfNeeded().forPath("/test/test1");
删除一个节点,强制指定版本进行删除:
client.delete().withVersion(1).forPath("/test/test1");
删除一个节点,强制保证删除:
client.delete().guaranteed().forPath("/test/test1");
一个完整的创建节点删除节点的例子:
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CreateBuilder;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class Demo1 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
CreateBuilder builder = client.create();
try {
builder.creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/test/test1","ceshi".getBytes());
client.delete().guaranteed().forPath("/test/test1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
异步接口:
Curator中引入了BackgroundCallback接口,用来处理异步接口调用。CreateBuilder提供了一个inBackground()方法可供使用,此接口就是Curator提供的异步调用入口。对应的异步处理接口为BackgroundCallback。此接口指提供了一个processResult的方法,用来处理回调结果。其中processResult的参数event中的getType()包含了各种事件类型,getResultCode()包含了各种响应码。
import org.apache.curator.RetryPolicy;
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.CreateBuilder;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import java.util.concurrent.Executors;
public class Demo2 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
CreateBuilder builder = client.create();
try {
builder.withMode(CreateMode.PERSISTENT).inBackground(new BackgroundCallback() {
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("当前线程:" + Thread.currentThread().getName() + ",code:"
+ event.getResultCode() + ",type:" + event.getType());
}
}, Executors.newFixedThreadPool(10)).forPath("/test");
builder.withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("当前线程:" + Thread.currentThread().getName() + ",code:"
+ event.getResultCode() + ",type:" + event.getType());
}
}).forPath("/test1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:如果自己指定了线程池,那么相应的操作就会在线程池中执行,如果没有指定,那么就会使用Zookeeper的EventThread线程对事件进行串行处理。
事件监听:
ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便,需要开发人员自己反复注册Watcher,比较繁琐。Curator 引入了Cache来实现对ZooKeeper服务端事件的监听。Cache是Curator 中对事件监听的包装,其对事件的监听其实可以近似看作是一个本地缓存视图和远程ZooKeeper视图的对比过程。同时Curator能够自动为开发人员处理反复注册监听,从而大大简化了原生API开发的繁琐过程。Cache分为两类监听类型:节点监听和子节点监听。
NodeCache:
NodeCache不仅可以用于监听数据节点的内容变更,也能监听指定节点是否存在。如果原本节点不存在,那么Cache 就会在节点被创建后触发NodeCachelistener。但是,如果该数据节点被删除,那么Curator就无法触发NodeCachelistener了。
import org.apache.curator.RetryPolicy;
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.CreateBuilder;
import org.apache.curator.framework.api.CuratorEvent;
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;
import java.util.concurrent.Executors;
public class Demo2 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
CreateBuilder builder = client.create();
try {
final NodeCache cache = new NodeCache(client,"/test/test1",false);
cache.start();
cache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("cache: "+cache.getCurrentData().getData());
}
});
builder.creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/test/test1","ceshi".getBytes());
client.setData().forPath("/test/test1","haha".getBytes());
client.delete().deletingChildrenIfNeeded().forPath("/test/test1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
PathChildrenCache:
PathChildrenCache用于监听指定ZooKeeper数据节点的子节点变化情况。
当指定节点的子节点发生变化时,就会回调该方法。PathChildrenCacheEvent类中定义了所有的事件类型,主要包括新增子节点(CHILD_ADDED)、子节点数据变更(CHILD_UPDATED)和子节点删除(CHILD_RE问OVED)三类。
import org.apache.curator.RetryPolicy;
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.CreateBuilder;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import java.util.concurrent.Executors;
public class Demo2 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
CreateBuilder builder = client.create();
try {
final PathChildrenCache cache = new PathChildrenCache(client,"/test",true);
cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
switch (pathChildrenCacheEvent.getType()){
case CHILD_ADDED:
System.out.println("CHILD_ADDED: "+pathChildrenCacheEvent.getData().getPath());
break;
case CHILD_UPDATED:
System.out.println("CHILD_UPDATED: "+pathChildrenCacheEvent.getData().getPath());
break;
case CONNECTION_SUSPENDED:
System.out.println("CONNECTION_SUSPENDED: "+pathChildrenCacheEvent.getData().getPath());
break;
case INITIALIZED:
System.out.println("INITIALIZED: "+pathChildrenCacheEvent.getData().getPath());
break;
case CONNECTION_RECONNECTED:
System.out.println("CONNECTION_RECONNECTED: "+pathChildrenCacheEvent.getData().getPath());
break;
case CHILD_REMOVED:
System.out.println("CHILD_REMOVED: "+pathChildrenCacheEvent.getData().getPath());
break;
case CONNECTION_LOST:
System.out.println("CONNECTION_LOST: "+pathChildrenCacheEvent.getData().getPath());
break;
}
}
});
builder.withMode(CreateMode.PERSISTENT).forPath("/test","ceshi".getBytes());
builder.withMode(CreateMode.PERSISTENT).forPath("/test/test1","ceshi".getBytes());
client.setData().forPath("/test/test1","haha".getBytes());
client.delete().deletingChildrenIfNeeded().forPath("/test/test1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. Curator应用#
4.1 master选举
有这样的场景:在分布式系统中,我们需要从集群中选举出一台机器作为master来分发任务。借助zk我们可以很方便的实现master选举功能。大体思路如下:
选择一个根节点,例如/master,多台机器同时向该节点创建一个子节点/master/lock ,利用ZooKeeper的特性,最终只有一台机器能够创建成功,成功的那台机器就作为Master。
Curator也是基于这个思路,但是它将节点创建、事件监听和自动选举过程进行了封装,开发人员只需要调用简单的API 即可实现Master选举。
import org.apache.curator.RetryPolicy;
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.CreateBuilder;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo2 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
ExecutorService excutor = Executors.newFixedThreadPool(20);
for(int i = 0;i<4;i++){
excutor.submit(new LeaderSelect(client));
}
}
}
class LeaderSelect implements Runnable{
private CuratorFramework client;
public LeaderSelect(CuratorFramework client) {
this.client = client;
}
@Override
public void run() {
createLeader();
}
private void createLeader (){
LeaderSelector selector = new LeaderSelector(client, "/test", new LeaderSelectorListener() {
@Override
public void takeLeadership(CuratorFramework curatorFramework) throws Exception {
System.out.println(Thread.currentThread().getName()+" 成为leader");
}
@Override
public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {
}
});
selector.autoRequeue();
selector.start();
}
}
输出结果:
Curator-LeaderSelector-2 成为leader
Curator-LeaderSelector-3 成为leader
Curator-LeaderSelector-1 成为leader
Curator-LeaderSelector-0 成为leader
Curator-LeaderSelector-2 成为leader
Curator-LeaderSelector-3 成为leader
Curator-LeaderSelector-1 成为leader
Curator-LeaderSelector-0 成为leader
Curator-LeaderSelector-2 成为leader
Curator-LeaderSelector-3 成为leader
Curator-LeaderSelector-1 成为leader
Curator-LeaderSelector-0 成为leader
Curator-LeaderSelector-2 成为leader
...
4.2 分布式锁
锁的问题经常会遇到,在分布式环境中更甚。zk实现分布式锁的逻辑是:各个节点同时在某个根节点”Lock”下创建临时顺序子节点:
/Lock/instance1_00001
/Lock/instance2_00002
/Lock/instance3_00003
...
然后对比谁的序号最小即谁获得锁。
那么Curator也是同理做了封装:InterProcessMutex类提供了分布式锁支持。
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Demo5 {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.131.128:2181",retryPolicy);
client.start();
final InterProcessMutex lock = new InterProcessMutex(client,"/test/test1");
for(int i = 0;i<30;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.acquire();
} catch (Exception e) {
e.printStackTrace();
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss|SSS");
String date = format.format(new Date());
System.out.println("date is : "+date);
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}
结果:
date is : 2018-05-05 21:40:55|129
date is : 2018-05-05 21:40:55|175
date is : 2018-05-05 21:40:55|193
date is : 2018-05-05 21:40:55|249
date is : 2018-05-05 21:40:55|266
date is : 2018-05-05 21:40:55|291
date is : 2018-05-05 21:40:55|301
date is : 2018-05-05 21:40:55|314
date is : 2018-05-05 21:40:55|335
date is : 2018-05-05 21:40:55|357
date is : 2018-05-05 21:40:55|377
date is : 2018-05-05 21:40:55|383
date is : 2018-05-05 21:40:55|393
date is : 2018-05-05 21:40:55|404
date is : 2018-05-05 21:40:55|411
date is : 2018-05-05 21:40:55|422
date is : 2018-05-05 21:40:55|426
date is : 2018-05-05 21:40:55|431
date is : 2018-05-05 21:40:55|439
date is : 2018-05-05 21:40:55|446
date is : 2018-05-05 21:40:55|456
date is : 2018-05-05 21:40:55|465
date is : 2018-05-05 21:40:55|472
date is : 2018-05-05 21:40:55|480
date is : 2018-05-05 21:40:55|488
date is : 2018-05-05 21:40:55|492
date is : 2018-05-05 21:40:55|502
date is : 2018-05-05 21:40:55|519
date is : 2018-05-05 21:40:55|535
date is : 2018-05-05 21:40:55|541
Process finished with exit code 0
4.3 分布式计数器
如果有需求是在分布式环境中统计系统访问人数,那么这个时候分布式计数器可以发挥作用。基于zk的分布式计数器实现思路也很简单:
指定一个zk数据节点作为计数器,多个应用实例在分布式锁的控制下,通过更新该数据节点的内容来实现计数功能。
Curator同样将这一系列逻辑封装在了DistributedAtomic开头的类中,从其类名我们可以看出这是一个可以在分布式环境中使用的原子整型。具体使用与java中的Atomic类一样:
RetryPolicy policy = new RetryNTimes(3,1000);
DistributedAtomicLong atomicLong = new DistributedAtomicLong(client,"/test",policy);
try {
atomicLong.increment();
} catch (Exception e) {
e.printStackTrace();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构