项目采用了微服务的模式,也就是说系统按一定的技术以及业务切分成各个独立的小系统,比如我们的产品是一个电商系统,那么可以分为:前端WAP,前端api,商品管理系统,采购系统,主数据管理系统,用户中心管理,价格管理系统,促销管理系统,订单管理系统,库存管理系统,门店管理系统等等,最后统计的数据是dubbo服务就高达18个,web系统有3个,前端WAP站点一个。这些系统要想跑起来就需要连接各种资源,比如服务地址,数据库,缓存,文件系统,消息队列等,一般项目中使用到的配置项大致是如下两类:资源以及具体业务相关。
配置中心的应用场景:
公司内存在多个系统,比如我们的web站点外加dubbo服务总超过20个,且系统之间的技术架构基本相同并且有一定的联系性
一套系统需要配置多个环境,我们有开发环境,测试环境,预上线环境,线上环境
配置中心需要解决的核心问题是多个系统配置信息统一管理困难的问题,这里我关心的功能如下:
从zookeeper中加载数据到bean管理器中
解决多环境取值问题,开发环境,测试环境,生产环境
zookeeper配置与本地配置兼容问题,通过一定手段可决定是使用zookeeper信息还是本地信息,比如本地调试时非常有用
zookeeper配置项发生变更后的更新问题
这里贴一张百度的disconf图,这个项目的功能更加强大,有兴趣可去研究:
首先我们看下系统中是如何使用的配置项,一般有两种用法:
a:某些XML配置文件中,比如:
b:程序中,一般是通过@Value这个注解来获取,比如我们可以写一个配置类来加载配置项:
@Servicepublic class MmsConfig { @Value("${es.cluster.name}") private String esClusterName; public String getEsClusterName() { return esClusterName;}
@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的一个子类
从zookeeper中获取一个配置项的Map,这里就不贴代码了
将Map一个一个填充到系统变量中,只要系统变量中有这些值,那么我们就可以直接按最上面的方式访问我们的属性值了
private void setSystemProperys(ConfigCenter cc, Mapconfig) { 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); } }
![](/blog/17071/201601/17071-20160128172143676-137379642.jpg)
写一个自定义的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就行,目的是让其先执行我们的后门程序,其它的使用不受影响,基本不需要修改原有代码。