背景
分布式动态配置参数,相信是很多公司都要做的,改一处配置,各个地方都可以用,最重要的是大多数情况下都希望能够再应用运行时修改配置参数而无需重启应用:
在这里,作者看了好多解决方案,但都不理想,所以自己来实现,主要如下:
- 使用百度开源的difconf 缺点:安装复杂依赖多
- 继承PropertyPlaceholderConfigurer 缺点:springboot已经不支持,或支持度不好
- 继承PropertySourcesPlaceholderConfigurer 缺点:实现很复杂,看不懂
- 使用数据库,每次取配置时取获取 缺点:对代码侵入大,而且对一些默认配置不好修改
找了很久,我认为比较简便的实现方式
- springboot 会把所有的配置存入到Environment中,然后再给应用运行提供参数,所以我们可以拦截这个Environment,替换其中的值
- 关于分布式动态,我们可以采用zookeeper,启动时从zookeeper获取配置,替换Environment的配置参数,同时监听节点,配置改变时,同步修改配置(配置目前实现是存在一个map里面)
- 示例使用的application.properties文件,当然你也可以换成其他配置文件,然后配置文件里面必须有loadPropFromRemote=true,来判断是否需要加载远程配置,反之使用本地配置
- zookeeper管理工具我用的是:https://github.com/zzhang5/zooinspector,当然github里面也有更好的工具,但很多占用资源多,部署也较麻烦,只需要修改配置,我觉得zooinspector够了
关键代码如下
- 在启动类里面增加一个bean,如下
@Autowired
private PropertyUtils propertyUtils;
/**
* 自动从zookeeper获取配置信息并重写本地配置文件
*
*/
@Bean
public String changeEnvironment(ApplicationContext applicationContext) {
//支持Environment获取 修改容器里面StandardServletEnvironment
StandardEnvironment standardEnvironment = applicationContext.getBean(StandardEnvironment.class);
//根据本地loadPropFromRemote的参数是否为true来确定是否要从远程zookeeper加载配置数据
if (StringUtils.isNotEmpty(standardEnvironment.getProperty("loadPropFromRemote")) && "true".equals(standardEnvironment.getProperty("loadPropFromRemote"))) {
Properties properties = propertyUtils.loadProperties(standardEnvironment);
standardEnvironment.getPropertySources().replace("applicationConfig: [classpath:/application.properties]", new PropertiesPropertySource("applicationConfig: [classpath:/application.properties]", properties));
}else {
Properties properties = (Properties)standardEnvironment.getPropertySources().get("applicationConfig: [classpath:/application.properties]").getSource();
propertyUtils.cacheProperties(properties);
}
return null;
}
- PropertyUtils
package com.wos.utils;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.logging.log4j.LogManager;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.S