统一配置中心
之前我的2015下半年总结中有提到我们的项目采用了微服务的模式,也就是说系统按一定的技术以及业务切分成各个独立的小系统,比如我们的产品是一个电商系统,那么可以分为:前端WAP,前端api,商品管理系统,采购系统,主数据管理系统,用户中心管理,价格管理系统,促销管理系统,订单管理系统,库存管理系统,门店管理系统等等,最后统计的数据是dubbo服务就高达18个,web系统有3个,前端WAP站点一个。这些系统要想跑起来就需要连接各种资源,比如服务地址,数据库,缓存,文件系统,消息队列等,一般项目中使用到的配置项大致是如下两类:资源以及具体业务相关。
配置中心的应用场景:
- 公司内存在多个系统,比如我们的web站点外加dubbo服务总超过20个,且系统之间的技术架构基本相同并且有一定的联系性
- 一套系统需要配置多个环境,我们有开发环境,测试环境,预上线环境,线上环境
配置中心需要解决的核心问题是多个系统配置信息统一管理困难的问题,这里我关心的功能如下:
- 从zookeeper中加载数据到bean管理器中
- 解决多环境取值问题,开发环境,测试环境,生产环境
- zookeeper配置与本地配置兼容问题,通过一定手段可决定是使用zookeeper信息还是本地信息,比如本地调试时非常有用
- zookeeper配置项发生变更后的更新问题
这里贴一张百度的disconf图,这个项目的功能更加强大,有兴趣可去研究:
首先我们看下系统中是如何使用的配置项,一般有两种用法:
a:某些XML配置文件中,比如:
<dubbo:protocol accesslog="true" name="dubbo" port="${zk.port}" />
b:程序中,一般是通过@Value这个注解来获取,比如我们可以写一个配置类来加载配置项:
@Service public class MmsConfig { @Value("${es.cluster.name}") private String esClusterName; public String getEsClusterName() { return esClusterName; }
这里的@Value的注解有两种用法:
- @Value("${es.cluster.name}")
- @Value("#{configProperties['es.cluster.name']}")
要搞清楚上面这两种用法,需要知道下面这几个类:
- PropertiesFactoryBean:这里官方给出的文档是:Allows for making a properties file from a classpath location available as Properties instance in a bean factory. Can be used to populate any bean property of type Properties via a bean reference.Supports loading from a properties file and/or setting local properties on this FactoryBean. The created Properties instance will be merged from loaded and local values. If neither a location nor local properties are set, an exception will be thrown on initialization.就是从指定的文档中读取配置信息并且加载到系统中,它在程序中可以使用上面的第二种方式。
- BeanFactoryPostProcessor:直接点就是对bean提供了属性值的管理
- PropertyPlaceholderConfigurer,实现了BeanFactoryPostProcessor接口,这个类比较高级,主要是替换点位符${...},它不光从文件中加载,还从系统变量以及环境变量中搜索相关key
- PreferencesPlaceholderConfigurer,它是PropertyPlaceholderConfigurer的一个子类
搞清楚了系统从配置文件中取值的逻辑,那么理解统一配置中心就不难了,无非就是在加载配置项的地方做些手脚让其按照我们的意图去获取更新配置项。这里我们应用一个已经非常成熟的产品zookepper,它的数据结果类似如下:
核心的功能就是从zookepper中获取配置项然后加载到系统变量中即可。我们看下如果将zookeeper中的配置项加载到系统中,根据PropertyPlaceholderConfigurer的功能描述,它会从三个地方去加载配置,我们选择将zookeeper配置加载到系统变量中,核心代码如下两步:
- 从zookeeper中获取一个配置项的Map,这里就不贴代码了
- 将Map一个一个填充到系统变量中,只要系统变量中有这些值,那么我们就可以直接按最上面的方式访问我们的属性值了
private void setSystemProperys(ConfigCenter 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); } }
不同环境的配置如何解决?
上面的功能只是提到了如何将zookepper中的配置加载到系统中,那么如何根据当前的环境加载正确的配置呢,这里也只需要在系统启动时传递一个环境变更即可,配置中心根据注入的环境变量值来判断应该加载哪个环境的数据。如果是非web项目,我们只需要在启动服务的命令中增加一个环境变更的参数即可:-Dmaven.test.skip=true clean install -Devn=sim,如果是web项目,我们可以通过Servelt配置文件来完成,最终通过ServletContexstListener来获取参数,流程如下所示:
写一个自定义的ServletContextListener,它的作用主要是从系统启动环境中获取变量,提供给配置中心使用
public class WanmeiContextLoaderListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { String evn = System.getProperty("evn"); if(evn == null || evn.equals("")) { evn = sce.getServletContext().getInitParameter("evn"); if (evn == null) { evn = "qa"; } System.setProperty("evn", evn); } } @Override public void contextDestroyed(ServletContextEvent sce) { // TODO Auto-generated method stub } }
zookeeper中的配置项发生变化后如何更新bean中的值呢?
我们可以利用guava提供的enventbus来解决,订阅一个zookeeper更新事件去更新系统变更即可,DataChangeEvent是自定义的一个类,要想实现自动更新需要写一些回调方法,也可以参考下这个项目:https://github.com/jamesmorgan/ReloadablePropertiesAnnotation
DataChangeEvent dataChangeEvent=new DataChangeEvent(map, DataChangeEvent.DataType.REMOTE, DataChangeEvent.ChangeType.DELETE); configOption.getEnventBus().post(dataChangeEvent);
如何配置呢?
只需要在PropertyPlaceholderConfigurer时加了一个depends-on就行,目的是让其先执行我们的后门程序,其它的使用不受影响,基本不需要修改原有代码。
<bean id="initSpringProperties" class="config.center.spring.SpringPropertyInjectSupport" lazy-init="false" init-method="init"> <property name="configNameSpaces" value="/configcenter/mms" /> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" depends-on="initSpringProperties"> <property name="locations"> <list> </list> </property> <property name="fileEncoding" value="UTF-8" /> </bean>
本文引用:
http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/
https://github.com/knightliao/disconf