之前我的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