文章目录
一、Apache Curator简介
-
解决了watcher的一次性的问题,注册一个watcher可以触发多次
-
Api简单易用
-
可以递归创建节点
-
提供ZooKeeper各种应用场景(recipe, 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装
-
提供了常用的Zookeeper工具类
-
提供了一套Fluent风格的操作API
二、Curator基本操作
1. 重试策略
一共有五种重试策略
-
重试策略ExponentialBackoffRetry,重试N次限制总的重试时间
baseSleepTimeMs
:两次重试之间的间隔时间maxRetries
:最大重试次数,如果超过次数就放弃maxSleepMs
:最大重试时间,如果超过该时间就放弃- 推荐的使用方式为:
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
-
重试策略RetryNTimes,重试N次
n
:重试的次数sleepMsBetweenRetries
:两次重试之间的间隔时间- 推荐的使用方式为:
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
-
重试策略RetryOneTime,重试一次
sleepMsBetweenRetry
:两次重试之间的间隔时间- 不推荐使用
-
重试策略RetryForever,一直在重试
retryIntervalMs
:两次重试之间的间隔时间- 不推荐使用
-
重试策略RetryUntilElapsed
maxElapsedTimeMs
:最大重试时间,重试时间超过maxElapsedTimeMs后,就不再重试sleepMsBetweenRetries
:两次重试之间的间隔时间- 推荐使用方式为:
RetryPolicy retryPolicy = new RetryUntilElapsed(2000, 3000);
2. 连接与关闭Zookeeper
//1 重试策略:初试时间为1s 重试10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.1.110:2181")//连接地址
.connectionTimeoutMs(3_000)//连接超时时间
.sessionTimeoutMs(30_000)//会话超时时间
.retryPolicy(retryPolicy)//重试策略
.namespace("super")//命名空间,连接后所有的操作都是在这个/super节点之下
.build();
//3 开启连接
client.start();
//4 关闭连接
client.close();
3. 创建节点
creatingParentsIfNeeded是递归创建节点,如果不存在父节点则同时会创建父节点
创建模式有以下几种:
CreateMode.PERSISTENT
:永久节点CreateMode.PERSISTENT_SEQUENTIAL
:永久顺序节点CreateMode.EPHEMERAL
:临时节点CreateMode.EPHEMERAL_SEQUENTIAL
:临时顺序节点
权限控制:
- 通过withACL,添加一个权限列表List<ACL>
- ACL类有两个属性
- 第一个是perms,可以通过Perms的枚举选择,代表了权限字符串列表:crdwa
- 第二个是Id,代表着五种权限机制,比如说world、digest等
- Id类,有两个属性
- 第一个是scheme,代表权限模式
- 第二个是id,代表权限模式后面的字符串,如digest模式,user:BASE64(SHA1(password))
List<ACL> acls = new ArrayList<ACL>();
Id demo1 = new Id("digest", AclUtils.getDigestUserPwd("demo1:123456"));
Id demo2 = new Id("digest", AclUtils.getDigestUserPwd("demo2:123456"));
acls.add(new ACL(Perms.ALL, demo1));
acls.add(new ACL(Perms.READ, demo2));
acls.add(new ACL(Perms.DELETE | Perms.CREATE, demo2));
创建节点举例:
String nodePath = "/super/demo";
byte[] data = "superme".getBytes();
// 创建节点
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)//创建模式
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//权限列表,如果想要将所有递归创建的节点都指定当前的权限,可以多一个参数true,如withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE,true)
.forPath(nodePath, data);
4. 删除和更新节点
String nodePath = "/super/demo";
// 更新节点数据
byte[] newData = "batman".getBytes();
client.setData().withVersion(0).forPath(nodePath, newData);
// 删除节点
client.delete()
.guaranteed() // 如果删除失败,那么在后端还是继续会删除,直到成功
.deletingChildrenIfNeeded() // 如果有子节点,就删除
.withVersion(0) // 版本号,如果不匹配会报错
.forPath(nodePath); // 删除的节点地址
5. 查询节点以及子节点
String nodePath = "/super/demo";
// 判断节点是否存在,如果不存在则为空
Stat statExist = cto.client.checkExists().forPath(nodePath + "/abc");
System.out.println(statExist);//statExist!=null,则代表节点存在
// 读取节点数据
Stat stat = new Stat();
byte[] data = client.getData()
.storingStatIn(stat)//将节点的状态信息也读取进来
.forPath(nodePath);
System.out.println("节点" + nodePath + "的数据为: " + new String(data));
System.out.println("该节点的版本号为: " + stat.getVersion());
// 查询子节点
List<String> childNodes = client.getChildren()
.forPath(nodePath);
System.out.println("开始打印子节点:");
for (String s : childNodes) {
System.out.println(s);
}
6. 创建watcher
一次性的创建方式:
String nodePath = "/super/demo";
// 添加watcher 事件 当使用usingWatcher的时候,监听只会触发一次,监听完毕后就销毁
cto.client.getData().usingWatcher((Watcher) event -> {
System.out.println("触发了watcher"+event);
}).forPath(nodePath);
cto.client.getData().usingWatcher((CuratorWatcher) event -> {
System.out.println("触发了watcher"+event);
}).forPath(nodePath);
重复使用父节点的watcher:
String nodePath = "/super/demo";
// NodeCache: 监听数据节点的变更,会触发事件
NodeCache nodeCache = new NodeCache(client, nodePath);
nodeCache.start(true);//buildInitial : 初始化的时候获取node的值并且缓存,true代表进行初始化,缓存节点值,默认为false
if (nodeCache.getCurrentData() != null) {
System.out.println("节点初始化数据为:" + new String(nodeCache.getCurrentData().getData()));
} else {
System.out.println("节点初始化数据为空...");
}
//创建监听器,当节点数据发生更改或者创建时,就会触发该方法
nodeCache.getListenable().addListener(() -> {
//如果是因为删除,导致获取不到节点
if (nodeCache.getCurrentData() == null) {
System.out.println("节点被删除了~");
return;
}
//获取数据
String data = new String(nodeCache.getCurrentData().getData());
//获取触发的节点路径
String path = nodeCache.getCurrentData().getPath();
System.out.println("节点路径:" + path + "数据:" + data);
});
重复使用子节点的watcher:
String nodePath = "/super/demo";
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,会触发事件
String childNodePathCache = nodePath;
//新建一个子节点缓存
PathChildrenCache childrenCache = new PathChildrenCache(client, childNodePathCache, true);//cacheData: 设置缓存节点的数据状态,如果为true,也会将子节点的状态信息缓存下来
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发初始化事件(推荐)
* NORMAL:异步初始化
* BUILD_INITIAL_CACHE:同步初始化
*/
childrenCache.start(StartMode.POST_INITIALIZED_EVENT);
//获取缓存的子节点数据
List<ChildData> childDataList = childrenCache.getCurrentData();
System.out.println("当前数据节点的子节点数据列表:");
for (ChildData cd : childDataList) {
String childData = new String(cd.getData());
System.out.println(childData);
}
//为子节点缓存添加监听器,可以对子节点触发的event的类型进行判断
childrenCache.getListenable().addListener((client, event) -> {
if (event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)) {//初始化事件触发
System.out.println("子节点初始化ok...");
} else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)) {//增加子节点
String path = event.getData().getPath();
if (path.equals(ADD_PATH)) {
System.out.println("添加子节点:" + event.getData().getPath());
System.out.println("子节点数据:" + new String(event.getData().getData()));
} else if (path.equals("/super/imooc/e")) {
System.out.println("添加不正确...");
}
} else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {//删除子节点
System.out.println("删除子节点:" + event.getData().getPath());
} else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {//子节点数据修改事件
System.out.println("修改子节点路径:" + event.getData().getPath());
System.out.println("修改子节点数据:" + new String(event.getData().getData()));
}
});
三、watcher统一配置修改
public class Client1 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.1.110:2181";
public Client1() {
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder()
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("workspace").build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public final static String CONFIG_NODE_PATH = "/super/demo";
public final static String SUB_PATH = "/redis-config";
public static CountDownLatch countDown = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
//连接Zookeeper
Client1 cto = new Client1();
System.out.println("client1 启动成功...");
//需要在父节点添加对子节点的监听
final PathChildrenCache childrenCache = new PathChildrenCache(cto.client, CONFIG_NODE_PATH, true);
childrenCache.start(StartMode.BUILD_INITIAL_CACHE);
// 添加监听器
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
// 监听节点变化
if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
String configNodePath = event.getData().getPath();
if (configNodePath.equals(CONFIG_NODE_PATH + SUB_PATH)) {
System.out.println("监听到配置发生变化,节点路径为:" + configNodePath);
// 读取节点数据
String jsonConfig = new String(event.getData().getData());
System.out.println("节点" + CONFIG_NODE_PATH + "的数据为: " + jsonConfig);
// 从json转换配置
RedisConfig redisConfig = null;
if (StringUtils.isNotBlank(jsonConfig)) {
redisConfig = JsonUtils.jsonToPojo(jsonConfig, RedisConfig.class);
}
// 配置不为空则进行相应操作
if (redisConfig != null) {
String type = redisConfig.getType();
String url = redisConfig.getUrl();
String remark = redisConfig.getRemark();
// 判断事件
if (type.equals("add")) {
System.out.println("监听到新增的配置,准备下载...");
// ... 连接ftp服务器,根据url找到相应的配置
Thread.sleep(500);
System.out.println("开始下载新的配置文件,下载路径为<" + url + ">");
// ... 下载配置到你指定的目录
Thread.sleep(1000);
System.out.println("下载成功,已经添加到项目中");
// ... 拷贝文件到项目目录
} else if (type.equals("update")) {
System.out.println("监听到更新的配置,准备下载...");
// ... 连接ftp服务器,根据url找到相应的配置
Thread.sleep(500);
System.out.println("开始下载配置文件,下载路径为<" + url + ">");
// ... 下载配置到你指定的目录
Thread.sleep(1000);
System.out.println("下载成功...");
System.out.println("删除项目中原配置文件...");
Thread.sleep(100);
// ... 删除原文件
System.out.println("拷贝配置文件到项目目录...");
// ... 拷贝文件到项目目录
} else if (type.equals("delete")) {
System.out.println("监听到需要删除配置");
System.out.println("删除项目中原配置文件...");
}
// TODO 视情况统一重启服务
}
}
}
}
});
countDown.await();
cto.closeZKClient();
}
}