场景
我们经常会遇到这样的场景:
1、一个活动系统刚上线,产品经理对你说这个活动还需要额外包含一个场景,只需要添加一个字段就可以,你说等下我重新修改下再发个包。猝
2、系统连接超时设置的是300ms,大促场景下并发提高了,时延到了500ms,想要重新设置,请等下我打包。又猝
3、系统出现故障,需要启动降级服务,降级服务通常是通过一个配置来开启的,默认是关闭的,现在要开启,你说还要重新打包修改配置发布。又又猝
其实以上场景都是配置更新或者添加的场景,如果每次配置都需要系统重新发包,走测试流程,再上线,不仅效率低下,开发也无法承受。所以一种实时动态的配置中心就显得尤为重要。
系统架构
我们这里就来介绍一种实时的配置中心实现方案,先看如下架构图:
管理后台承载所有业务的配置写入,配置首先写入到DB(例如MongoDB),通过DB的主从复制,或者MongoShake写入到从数据库;
task主要负责定时从DB拉取数据写入到zookeeper;
不同的业务通过嵌入配置中心的SDK实时感知zk的配置字段变化,加载到业务内容,实现动态感知。
实现
对于后台写入到DB以及task同步到zk的实现都较为容易,这里主要讲下zk的数据存储和实时感知的实现。
先定义一个配置基于zk操作的接口configYard,里面定义配置的增删改查的各种操作,同时还包括配置重载,如下:
public interface ConfigYard {
/**
* 配置平台根节点名称
*/
static String yardRoot = "/yard";
/**
* 初始化配置
*/
void init();
/**
* 重新加载配置资源
*/
void reload();
/**
* 添加配置
* @param key
* @param value
*/
void add(String key, String value);
/**
* 更新配置
* @param key
* @param value
*/
void update(String key, String value);
/**
* 删除配置
* @param key
*/
void delete(String key);
/**
* 获取配置
* @param key
* @return
*/
String get(String key);
/**
* 获取所有的配置内容
* @return
*/
Map<String, String> getAll();
}
实现此接口,我们只讨论get和reload操作,其它的和get差不多。
public String get(String key) {
//如果当前内存没有则访问zk获取
if(this.yardProperties.get(key) == null){
String contactKey = this.contactKey(key);
if(!this.client.exists(contactKey)){
return null;
}
//异步触发重载
asyncReload();
return this.client.readData(contactKey);
}
//本地内存获取
return yardProperties.get(key);
}
Reload的操作一般是当本地主动获取是没有以及zk监控节点发生变化是会触发,它主要是将zk监控路径下的所有key和value全部加载一遍到内存,如下实现:
public void reload() {
List<String> yardList = this.client.getChildren(yardRoot);
Map<String, String> currentYardProperties = new HashMap<String, String>();
for(String yard : yardList){
String value = this.client.readData(this.contactKey(yard));
currentYardProperties.put(yard, value);
}
yardProperties = currentYardProperties;
}
最后还需要再处理一下的就是对zk监控节点事件的处理,所有监控路径下的变动都触发reload操作。
private class ConfigYardListener implements IZkDataListener,IZkChildListener{
public void handleDataChange(String dataPath, Object data)
throws Exception {
logger.info("data {} change,start reload configProperties",dataPath);
configYard.reload();
}
public void handleDataDeleted(String dataPath) throws Exception {
logger.info("data {} delete,start reload configProperties",dataPath);
configYard.reload();
}
public void handleChildChange(String parentPath,
List<String> currentChilds) throws Exception {
logger.info("data {} ChildChange,start reload configProperties",parentPath);
configYard.reload();
}
}
推荐阅读
架构实践系列: 分布式唯一ID生成方案 如何解决并发场景下扣款的数据一致性问题? 如何保障mysql和redis之间的数据一致性? 高并发系统下的缓存解决方案 Redis如何实现异地多活? 如何提升支付系统热点账户冲扣性能? 高并发高性能的DB数据复制方案 再谈Redis双活实现方案 高并发系统的限流方案如何实现? 高并发系统下的降级如何实现? 彻底弄懂Redis的内存淘汰策略
扫码关注我们
互联网架构师之路
过滤技术杂质,只为精品呈现
如果喜欢,请关注加星喔