标签:doc 网上 info 转换 rpo inline string 节点 连接
统一配置中心1中记录了我之前项目中如何处理多系统中的配置问题,对于统一配置中心组件一般分为两种做法:
它的好外与缺点都非常明确。
开源的配置中心,我之前有提到过百度的disconf,还有当当的config-toolkit,这些产品都有很多应用案例,功能也非常全。
当系统越来越多后,每个系统的配置如果没有统一的管理机制那么会非常难管理,需要有一种侵入性小的方案来将这些原本在单项目中维护的配置工作统一管理起来。
推荐使用zookeeper来存储项目中的配置项,也可以是其它的存储。下面设计的配置中心组件是可扩展,但实现暂时只实现了zookeeper。
在系统加载时,想办法将存储在zookeeper中的配置内容加载到系统变量中,然后程序就可以像调用本地配置文件一样了,不需要改变现有系统引用变量的行为。至于本地配置文件直接使用Spring自带功能即可,当然也可以统一在组件中。
不需要支持本地文件的热更新
内容有点多,先看下配置加载的时序图,不包含事件通知以及热更新。
PropertyPlaceholderConfigurer,可以利用它来启动组件的初始化进而从zookeeper中加载数据到系统中,即先加载zookeeper数据然后加载本地配置文件。
@Bean
public static PropertyPlaceholderConfigurer properties() {
SpringPropertyInjectSupport springPropertyInjectSupport=new SpringPropertyInjectSupport();
springPropertyInjectSupport.setConfigNameSpaces("configcenter/"+System.getProperty("env"));
springPropertyInjectSupport.init();
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[]
{ new ClassPathResource( "application.properties" ) };
ppc.setLocations( resources );
ppc.setIgnoreUnresolvablePlaceholders( true );
return ppc;
}
SpringPropertyInjectSupport,是自定义的组件启动类,它负责加载zookeeper中的数据到系统:核心方法是init。
public void init() {
if (this.configNameSpaces != null) {
this.setSystemPropertiesFromConfigCenter();
}
}
configNameSpaces是统一配置中心管理所有节点的父结点,比如configcenter/test是指测试环境的配置,系统中的配置节点名称不包含这些与框架相关的信息,只需要配置dataSource=XXX即代表configcenter/test/dataSource这个配置项。
ConfigCenterFactory是个单例用来返回统一配置中心实例,ConfigCenterService是配置接口,然后由配置接口获取所有的配置项(类型是一个Map),最后将Map中的信息导入到系统变量中。
private void setSystemPropertiesFromConfigCenter() {
if (StringUtils.isBlank(this.configNameSpaces)) {
return;
}
ConfigCenterFactory.getInstance().setSystemNameSpace(this.configNameSpaces);
ConfigCenterService cc = ConfigCenterFactory.getInstance().getConfig(this.configNameSpaces);
Map<String, Object> config = cc.getConfig();
setSystemProperys(cc, config);
}
private void setSystemProperys(ConfigCenterService cc, Map<String, Object> config) {
for (String key : config.keySet()) {
String value = cc.get(key);
if (key.contains(".")) {
key = key.substring(1);
}
if (value == null) {
value = "";
}
System.setProperty(key, value);
}
}
实例化配置组件,为了支持同时加载多个不同节点下的数据,所以以nameSpace做为key将实例放在Map中,比如想同时访问商品以及订单的配置,它们的namespace分别为
private static final Object lockObj = new Object();
private ConcurrentHashMap<String, ConfigCenterService> configCenterCache = null;
public ConfigCenterService getConfig(final String hosts, final String nameSpace) {
Preconditions.checkNotNull(hosts);
Preconditions.checkNotNull(nameSpace);
StringBuilder sb = new StringBuilder(hosts);
sb.append(nameSpace);
final String key = sb.toString().intern();
ConfigCenterService config = this.configCenterCache.get(key);
if (config == null) {
synchronized (lockObj) {
if (!this.configCenterCache.containsKey(key)) {
ConfigOption co = new ConfigOption(nameSpace, hosts);
ConfigCenterService cc = new ConfigCenterServiceImpl(co);
this.configCenterCache.put(key, cc);
}
}
} else {
return config;
}
return this.configCenterCache.get(key);
}
配置管理接口,主要包含三部分内容:
不是更新系统中的变量,是指系统中的变量发生改变之后需要做什么?比如数据库的配置发生变化,此时需要重新刷新数据库连接池的信息等。
一些获取配置的协助类,即强类型化返回配置,减少调用端的类型转换。
public interface ConfigCenterService {
public Map<String,Object> getConfig();
public String get(String key);
public Long getLongValue(String key);
public Integer getIntegerValue(String key);
public Double getDoubleValue(String key);
public Boolean getBooleanValue(String key);
public void notify(DataChangeEvent event);
public void colse();
}
一些通用的逻辑实现放在这里。
是具体的配置接口实现类,继承AbstractConfigCenterService,实现ConfigCenterService接口。
private void startClient() {
if (this.client == null) {
try {
this.client = CuratorFrameworkFactory.builder()
.connectString(configOption.getZkUrls())
.namespace(configOption.getNameSpace())
.retryPolicy(configOption.getRetryPolicy())
.connectionTimeoutMs(20000)
.build();
this.client.start();
logger.info("zkclient start " + configOption.getNameSpace());
} catch (Throwable e) {
if(null!=this.client){
this.client.close();
}
throw new RuntimeException("CuratorFrameworkFactory start error",e);
}
}
}
private void loadConfig(String nodePath) {
try {
if (StringUtils.isNotEmpty(nodePath)) {
loadData(nodePath);
}
GetChildrenBuilder childrenBuilder = client.getChildren();
try {
List<String> children = null;
if (StringUtils.isEmpty(nodePath)) {
children = childrenBuilder.watched().forPath(null);
} else {
children = childrenBuilder.watched().forPath(nodePath);
}
loadChildsConfig(children, nodePath);
} catch (Exception e) {
throw Throwables.propagate(e);
}
} catch (Exception e) {
logger.error("load zk config error namespace={}", configOption.getNameSpace());
logger.error("load config error", e);
this.client.close();
throw new RuntimeException("load zk error");
}
}
private Watcher getPathWatcher() {
return new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event != null) {
try {
boolean isDelete = false;
if (event.getState() == Event.KeeperState.SyncConnected) {
String path = event.getPath();
if (path == null || path.equals("/")) return;
switch (event.getType()) {
case NodeDeleted:
postRemovePath(event.getPath());
isDelete = true;
break;
case NodeDataChanged:
postDataChangeEvent(event);
break;
default:
break;
}
if (!isDelete) {
watchPathDataChange(event.getPath());
}
}
} catch (Exception e) {
logger.info("zk data changed error:",e);
}
}
}
};
}
节点数据变更后的事件通知,采用guava的eventbus来实现。
private void postDataChangeEvent(WatchedEvent event) throws Exception {
byte[] data = client.getData().forPath(event.getPath());
String value = new String(data, Charsets.UTF_8);
String key = event.getPath().replace("/", ".");
postDataChangeKeyValue(key, value);
}
private void postDataChangeKeyValue(String key, String value) {
this.config.put(key, value);
Map<String, Object> map = new HashMap<String, Object>();
map.put(key, value);
DataChangeEvent dataChangeEvent = new DataChangeEvent(map);
configOption.getEnventBus().post(dataChangeEvent);
}
统一配置源码
源码参考了网上开源的项目,由于时间久远原项目已经找到地址了。
我公布的源码是经过我重新整理后的结果,并非全部出自于自己。
标签:doc 网上 info 转换 rpo inline string 节点 连接
原文地址:http://www.cnblogs.com/ASPNET2008/p/6752131.html