统一配置中心

  以前个人2015下半年总结中有提到咱们的项目采用了微服务的模式,也就是说系统按必定的技术以及业务切分红各个独立的小系统,好比咱们的产品是一个电商系统,那么能够分为:前端WAP,前端api,商品管理系统,采购系统,主数据管理系统,用户中心管理,价格管理系统,促销管理系统,订单管理系统,库存管理系统,门店管理系统等等,最后统计的数据是dubbo服务就高达18个,web系统有3个,前端WAP站点一个。这些系统要想跑起来就须要链接各类资源,好比服务地址,数据库,缓存,文件系统,消息队列等,通常项目中使用到的配置项大体是以下两类:资源以及具体业务相关。html

  配置中心的应用场景:前端

  •   公司内存在多个系统,好比咱们的web站点外加dubbo服务总超过20个,且系统之间的技术架构基本相同而且有必定的联系性
  •   一套系统须要配置多个环境,咱们有开发环境,测试环境,预上线环境,线上环境

  配置中心须要解决的核心问题是多个系统配置信息统一管理困难的问题,这里我关心的功能以下:git

  • 从zookeeper中加载数据到bean管理器中
  • 解决多环境取值问题,开发环境,测试环境,生产环境
  • zookeeper配置与本地配置兼容问题,经过必定手段可决定是使用zookeeper信息仍是本地信息,好比本地调试时很是有用
  • zookeeper配置项发生变动后的更新问题

  这里贴一张百度的disconf图,这个项目的功能更增强大,有兴趣可去研究:github

   首先咱们看下系统中是如何使用的配置项,通常有两种用法:web

     a:某些XML配置文件中,好比:spring

<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的注解有两种用法:api

  • @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

相关文章
相关标签/搜索