接着上篇博客:
本文将实现上篇博客最后所述的问题:项目启动时加载指定环境的指定路径下的节点信息
因为需要预加载指定路径下的节点信息,所以使用PathChildrenCache来存储节点信息,使用对应的PathChildrenCacheListener来监听节点状态变化。
第一步:配置文件中指定预加载的节点路径
第二步:自定义Zookeeper客户端ZKExtClient.java,和上篇博客定义的客户单ZKClient.java类似,差别在于使用存储节点方式不同,本案例使用的是PathChildrenCache。
package com.learn.zw.zookeeper.client;
import com.learn.zw.zookeeper.constant.ZKConstant;
import com.learn.zw.zookeeper.starter.config.ZKConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName: ZKExtClient
* @Description: TODO
* @Author: zw
* @Date: 2019/3/8 14:52
* @Version: 1.0
*/
@Slf4j
public class ZKExtClient {
private CuratorFramework client;
private Map<String, Object> cache = new ConcurrentHashMap<String, Object>();
public ZKExtClient(ZKConfig config) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(
config.getRetrySleepTime(), config.getRetryCount());
client = CuratorFrameworkFactory.builder().connectString(config.getConnectionAddr())
.connectionTimeoutMs(config.getConnectionTimeout())
.sessionTimeoutMs(config.getSessionTimeout())
.retryPolicy(retryPolicy)
.namespace(ZKConstant.root)
.build();
client.start();
}
public CuratorFramework getClient() {
return client;
}
public PathChildrenCache getPathChildrenCache(String path) {
return (PathChildrenCache)cache.get(path);
}
public void setData(String path, CreateMode mode, byte[] data) {
try {
if (!isExist(path)) {
client.create().withMode(mode).forPath(path, data);
return;
}
client.setData().forPath(path, data);
} catch (Exception e) {
log.error("更新属性节点:{},发生异常:{}", path, e);
}
}
public void setData(String path, byte[] data) {
try {
if (!isExist(path)) {
client.create().withMode(CreateMode.PERSISTENT).forPath(path, data);
return;
}
client.setData().forPath(path, data);
} catch (Exception e) {
log.error("更新属性节点:{},发生异常:{}", path, e);
}
}
public void setDataParentsIfNeeded(String path, byte[] data) {
try {
if (!isExist(path)) {
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, data);
return;
}
client.setData().forPath(path, data);
} catch (Exception e) {
log.error("更新/新增属性节点:{},发生异常:{}", path, e);
}
}
public byte[] getData(String path) {
try {
if (!isExist(path)) {
return null;
}
return client.getData().forPath(path);
} catch (Exception e) {
log.error("获取属性节点:{},发生异常:{}", path, e);
return null;
}
}
public boolean deleteData(String path) {
try {
if (isExist(path)) {
client.delete().deletingChildrenIfNeeded().forPath(path);
}
} catch (Exception e) {
log.error("删除属性节点:{},发生异常:{}", path, e);
return false;
}
return true;
}
public boolean isExist(String path) {
try {
Stat stat = client.checkExists().forPath(path);
return stat == null ? false : true;
} catch (Exception e) {
log.error("校验属性节点:{},是否存在发生异常:{}", path, e);
return false;
}
}
/**
* 设置Path Cache, 监控本节点的子节点被创建,更新或者删除,注意是子节点, 子节点下的子节点不能递归监控
* 事件类型有3个, 可以根据不同的动作触发不同的动作
* @Param path 监控的节点路径, cacheData 是否缓存data
* 可重入监听
* */
public void registerPathChildrenCacheListener(PathChildrenCacheListener listener, String path, boolean cacheData) {
try {
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, cacheData);
pathChildrenCache.getListenable().addListener(listener);
pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
cache.put(path, pathChildrenCache);
} catch (Exception e) {
log.error("节点:{},注册PathChildrenCacheListener监听发生异常:{}", path, e);
}
}
/**
* 设置Node Cache, 监控本节点的新增,删除,更新
* 节点的update可以监控到, 如果删除会自动再次创建空节点
* @Param path 监控的节点路径, dataIsCompressed 数据是否压缩
* 不可重入监听
* */
public void registerNodeCacheListener(NodeCacheListener listener, String path, boolean dataIsCompressed) {
}
/**
* 设置Tree Cache, 监控本节点的新增,删除,更新
* 节点的update可以监控到, 如果删除不会自动再次创建
* @Param path 监控的节点路径, dataIsCompressed 数据是否压缩
* 可重入监听
* */
public void registerTreeCacheListener(TreeCacheListener listener, String path) {
}
/**
* 注册zookeeper连接状态监听器
* @param stateListener
*/
public void registerConnectionStateListener(ConnectionStateListener stateListener) {
this.client.getConnectionStateListenable().addListener(stateListener);
}
}
第三步:自定义属性操作类PropertyExtCache.java,新增属性栏目用来指定预加载的节点路径
package com.learn.zw.zookeeper.cache;
import com.learn.zw.zookeeper.client.ZKExtClient;
import com.learn.zw.zookeeper.constant.ZKConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
/**
* @ClassName: PropertyExtCacheClient
* @Description: TODO
* @Author: zw
* @Date: 2019/3/7 16:22
* @Version: 1.0
*/
@Slf4j
public class PropertyExtCache {
private ZKExtClient zkClient;
/**
* 环境变量
*/
private String profile;
/**
* 属性栏目
*/
private String[] modules;
public PropertyExtCache(ZKExtClient client, String profile, String[] modules) {
log.info("属性预加载环境:{}", profile);
this.profile = profile;
this.zkClient = client;
this.modules = modules;
if (modules != null && modules.length > 0) {
for (int i = 0; i < modules.length; i++) {
String module = modules[i];
log.info("已订阅栏目路径:{}", module);
this.zkClient.registerPathChildrenCacheListener(new CacheListener1(ZKConstant.separator + profile + module),
ZKConstant.separator + profile + module, true);
}
} else {
log.warn("未发现待订阅栏目路径");
}
}
/**
* 根据属性节点路径从缓存中获取属性值
*
* @param path
* @return
* @throws Exception
*/
public String getPropertyWithCache(String path) throws Exception {
String key = path.substring(0, path.lastIndexOf("/"));
PathChildrenCache pathChildrenCache = zkClient.getPathChildrenCache(getPrefixPath() + key);
if (pathChildrenCache == null) {
log.warn("预加载环境未发现路径为:{}的属性节点", getPrefixPath() + key);
return null;
}
final ChildData currentData = pathChildrenCache.getCurrentData(getPrefixPath() + path);
if (currentData == null) {
log.warn("未发现路径为:{}的属性节点", path);
return null;
}
byte[] bytes = currentData.getData();
String data = new String(bytes, "utf-8");
log.info("获取当前属性路径:" + path + ",属性值:" + (data.length() > 15 ? data.substring(0, 15) : data));
return data;
}
/**
* 删除属性节点,级联删除字节点
*
* @param path
*/
public void deleteProperty(String path) {
zkClient.deleteData(getPrefixPath() + path);
}
/**
* 添加属性节点,父路径必须已存在
*
* @param path
* @param value
*/
public void addProperty(String path, String value) {
zkClient.setData(getPrefixPath() + path, value.getBytes());
}
/**
* 添加属性节点,父路径可不存在
*
* @param path
* @param value
*/
public void addPropertyParentsIfNeeded(String path, String value) {
zkClient.setDataParentsIfNeeded(getPrefixPath() + path, value.getBytes());
}
/**
* 获取当前环境下根节点路径,如:/dev,/prd
*
* @return
*/
public String getPrefixPath() {
return ZKConstant.separator + profile;
}
/**
* 自定义节点状态监听器
*/
class CacheListener1 implements PathChildrenCacheListener {
private String path;
public CacheListener1(String path) {
this.path = path;
}
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
log.info("监听路径:{},新增属性节点CHILD_ADDED,属性路径:{},属性值:{}", path,
event.getData().getPath(), new String(event.getData().getData(), "utf-8"));
break;
case CHILD_UPDATED:
log.info("监听路径:{},更新属性节点CHILD_UPDATED,属性路径;{},属性值:{}", path,
event.getData().getPath(), new String(event.getData().getData(), "utf-8"));
break;
case CHILD_REMOVED:
log.info("监听路径:{},移除属性节点NODE_REMOVED,属性路径;{},属性值:{}", path,
event.getData().getPath(), new String(event.getData().getData(), "utf-8"));
break;
case CONNECTION_LOST:
log.warn("监听路径:{},连接丢失:CONNECTION_LOST", path);
break;
case CONNECTION_SUSPENDED:
log.warn("监听路径:{},连接挂起:CONNECTION_SUSPENDED", path);
break;
case CONNECTION_RECONNECTED:
log.warn("监听路径:{},重新连接:CONNECTION_RECONNECTED", path);
break;
case INITIALIZED:
log.warn("监听路径:{},连接初始化:INITIALIZED", path);
break;
default:
log.warn("监听到未定义状态变化类型");
break;
}
}
}
}
第四步:定义bean的加载类ZKBoot.java
package com.learn.zw.zookeeper.starter;
import com.learn.zw.zookeeper.cache.PropertyCache;
import com.learn.zw.zookeeper.cache.PropertyExtCache;
import com.learn.zw.zookeeper.client.ZKExtClient;
import com.learn.zw.zookeeper.starter.config.ZKConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZKBoot {
@Value("${learn.zk.profile}")
private String profile;
@Value("${learn.zk.modules}")
private String[] modules;
/**
* 此bean用来操作PathChildrenCache节点,可加载多个不同的路径下的一级子节点
* 注:PathChildrenCache特性
* @param config
* @return
*/
@Bean
public ZKExtClient zkExtClient(@Qualifier("zkConfig") ZKConfig config) {
return new ZKExtClient(config);
}
@Bean
public PropertyExtCache propertyExtCache(ZKExtClient zkExtClient) {
return new PropertyExtCache(zkExtClient, profile, modules);
}
/**
* 此bean用来操作TreeCache类型节点,加载指定路径下所有节点
* 注:TreeCahce特性
* @param config
* @return
*/
//@Bean
public PropertyCache propertyCache(@Qualifier("zkConfig") ZKConfig config) {
return new PropertyCache(config, profile);
}
}
第五步:通过ZookeeperController.java定义的请求接口进行测试
package com.learn.zw.zookeeper.controller;
import com.learn.zw.zookeeper.cache.PropertyCache;
import com.learn.zw.zookeeper.cache.PropertyExtCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: TestController
* @Description: TODO
* @Author: zw
* @Date: 2019/3/7 18:23
* @Version: 1.0
*/
@RestController
public class ZookeeperController {
//@Autowired
private PropertyCache propertyCache;
@Autowired
private PropertyExtCache propertyExtCache;
@RequestMapping(value = "/test/get")
public String test(String path) {
try {
//String property = propertyCache.getProperty(path);
String property = propertyExtCache.getPropertyWithCache(path);
return property;
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
@RequestMapping(value = "/test/delete")
public String delete(String path) {
try {
//propertyCache.deleteProperty(path);
propertyExtCache.deleteProperty(path);
return "success";
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
@RequestMapping(value = "/test/add")
public String add(String path, String value) {
try {
propertyExtCache.addPropertyParentsIfNeeded(path, value);
//propertyCache.addPropertyParentsIfNeeded(path, value);
return value;
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
}
第六步:测试结果分析
1)通过yml配置文件中的modules可以知道预加载时只加载了/user/base、/user/center、/member/base路径下节点信息,对于其他路径下的节点信息是获取不到的,也监听不到其他节点的状态变化。通过zookeeper命令,查看一下目前zookeeper中共存储了哪些节点,如图:
获取未预加载路径下的节点信息时为空
获取已预加载路径下的节点信息可以获取到
2)新增节点
3)删除节点
4)后台日志
2019-04-06 12:02:04.955 WARN 3626 --- [nio-8090-exec-5] c.l.zw.zookeeper.cache.PropertyExtCache : 预加载环境未发现路径为:/dev/level1的属性节点
2019-04-06 12:05:31.810 INFO 3626 --- [nio-8090-exec-8] c.l.zw.zookeeper.cache.PropertyExtCache : 获取当前属性路径:/user/base/addrcode,属性值:address coce
2019-04-06 12:08:25.399 INFO 3626 --- [ChildrenCache-2] c.l.zw.zookeeper.cache.PropertyExtCache : 监听路径:/dev/member/base,新增属性节点CHILD_ADDED,属性路径:/dev/member/base/addr,属性值:chengyuandedizhixinxi
2019-04-06 12:09:26.731 INFO 3626 --- [ChildrenCache-2] c.l.zw.zookeeper.cache.PropertyExtCache : 监听路径:/dev/member/base,移除属性节点NODE_REMOVED,属性路径;/dev/member/base/addr,属性值:chengyuandedizhixinxi
总结
以上就是zookeeper实现配置管理的全部内容了,基本上都是代码实现,想理解zookeeper客户端以及一些关于zookeeper操作的高级API的可以在网上查阅相关资料学习,先知道它们是干什么的然后再根据个人情况和能力去深究其原理,不懂可以多看几遍但一定一定不要放弃,还有可别忘了动手实现,不动手玩玩那都是白瞎。
一起学习,一起进步!