統一配置中心

  以前個人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

相關文章
相關標籤/搜索