之前我的2015下半年总结中有提到我们的项目采用了微服务的模式,也就是说系统按一定的技术以及业务切分成各个独立的小系统,比如我们的产品是一个电商系统,那么可以分为:前端WAP,前端api,商品管理系统,采购系统,主数据管理系统,用户中心管理,价格管理系统,促销管理系统,订单管理系统,库存管理系统,门店管理系统等等,最后统计的数据是dubbo服务就高达18个,web系统有3个,前端WAP站点一个。这些系统要想跑起来就需要连接各种资源,比如服务地址,数据库,缓存,文件系统,消息队列等,一般项目中使用到的配置项大致是如下两类:资源以及具体业务相关。
配置中心的应用场景:
配置中心需要解决的核心问题是多个系统配置信息统一管理困难的问题,这里我关心的功能如下:
这里贴一张百度的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的注解有两种用法:
要搞清楚上面这两种用法,需要知道下面这几个类:
搞清楚了系统从配置文件中取值的逻辑,那么理解统一配置中心就不难了,无非就是在加载配置项的地方做些手脚让其按照我们的意图去获取更新配置项。这里我们应用一个已经非常成熟的产品zookepper,它的数据结果类似如下:
核心的功能就是从zookepper中获取配置项然后加载到系统变量中即可。我们看下如果将zookeeper中的配置项加载到系统中,根据PropertyPlaceholderConfigurer的功能描述,它会从三个地方去加载配置,我们选择将zookeeper配置加载到系统变量中,核心代码如下两步:
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