1. 分佈式配置管理平臺Disconf
1.1. 摘要
爲了更好的解決分佈式環境下多臺服務實例的配置統一管理問題,本文提出了一套完整的分佈式配置管理解決方案(簡稱爲disconf[4],下同)。首先,實現了同構系統的配置發佈統一化,提供了配置服務server,該服務能夠對配置進行持久化管理並對外提供restful接口,在此基礎上,基於zookeeper實現對配置更改的實時推送,而且,提供了穩定有效的容災方案,以及用戶體驗良好的編程模型和WEB用戶管理界面。其次,實現了異構系統的配置包管理,提出基於zookeeper的全局分佈式一致性鎖來實現主備統一部署、系統異常時的主備自主切換。經過在百度內部以及外部等多個產品線的實踐結果代表,本解決方案是有效且穩定的。html
1.2. 技術背景
在一個分佈式環境中,同類型的服務每每會部署不少實例。這些實例使用了一些配置,爲了更好地維護這些配置就產生了配置管理服務。經過這個服務能夠輕鬆地管理成千上百個服務實例的配置問題。前端
王阿晶提出了基於zooKeeper的配置信息存儲方案的設計與實現[1], 它將全部配置存儲在zookeeper上,這會致使配置的管理不那麼方便,並且他們沒有相關的源碼實現。淘寶的diamond[2]是淘寶內部使用的一個管理持久配置的系統,它具備完整的開源源碼實現,它的特色是簡單、可靠、易用,淘寶內部絕大多數系統的配置都採用diamond來進行統一管理。他將全部配置文件裏的配置打散化進行存儲,只支持KV結構,而且配置更新的推送是非實時的。百度內部的BJF配置中心服務[3]採用了相似淘寶diamond的實現,也是配置打散化、只支持KV和非實時推送。java
同構系統是市場的主流,特別地,在業界大量使用部署虛擬化(如JPAAS系統,SAE,BAE)的狀況下,同一個系統使用同一個部署包的情景會愈來愈多。可是,異構系統也有必定的存在乎義,譬如,對於「拉模式」的多個下游實例,同一時間點只能只有一個下游實例在運行。在這種情景下,就存在多臺實例機器有「主備機」模式的問題。目前國內並無很明顯的解決方案來統一解決此問題。python
1.3. 功能特色與設計理念
disconf是一套完整的基於zookeeper的分佈式配置統一解決方案。mysql
它的功能特色是linux
- 支持配置(配置項+配置文件)的分佈式化管理
- 配置發佈統一化
- 配置發佈、更新統一化(雲端存儲、發佈):配置存儲在雲端系統,用戶統一在平臺上進行發佈、更新配置。
- 配置更新自動化:用戶在平臺更新配置,使用該配置的系統會自動發現該狀況,並應用新配置。特殊地,若是用戶爲此配置定義了回調函數類,則此函數類會被自動調用。
- 配置異構系統管理
- 異構包部署統一化:這裏的異構系統是指一個系統部署多個實例時,因爲配置不一樣,從而須要多個部署包(jar或war)的狀況(下同)。使用Disconf後,異構系統的部署只須要一個部署包,不一樣實例的配置會自動分配。特別地,在業界大量使用部署虛擬化(如JPAAS系統,SAE,BAE)的狀況下,同一個系統使用同一個部署包的情景會愈來愈多,Disconf能夠很天然地與他自然契合。 異構主備自動切換:若是一個異構系統存在主備機,主機發生掛機時,備機能夠自動獲取主機配置從而變成主機。
- 異構主備機Context共享工具:異構系統下,主備機切換時可能須要共享Context。可使用Context共享工具來共享主備的Context。
- 註解式編程,極簡的使用方式:咱們追求的是極簡的、用戶編程體驗良好的編程方式。經過簡單的標註+極簡單的代碼撰寫,便可完成複雜的配置分佈式化。
- 須要Spring編程環境
它的設計理念是:nginx
- 簡單,用戶體驗良好:
- 摒棄了打散化配置的管理方式[2,3],仍舊採用基於配置文件的編程方式,這和程序員之前的編程習慣(配置都是放在配置文件裏)一致。特別的,爲了支持較爲小衆的打散化配置功能,還特別支持了配置項。
- 採用了基於XML無代碼侵入編程方式:只須要幾行XML配置,便可實現配置文件發佈更新統一化、自動化。
- 採用了基於註解式的弱代碼侵入編程方式:經過編程規範,一個配置文件一個配置類,代碼結構簡單易懂。XML幾乎沒有任何更改,與原springXML配置同樣。真正編程時,幾乎感受不到配置已經分佈式化
- 能夠託管任何類型的配置文件,這與[2,3]只能支持KV結構的功能有較大的改進。
- 配置更新實時推送
- 提供界面良好Web管理功能,能夠很是方便的查看配置被哪些實例使用了。
1.4. 詳細設計
架構設計
disconf服務集羣模式:git
![image0](http://static.javashuo.com/static/loading.gif)
disconf的模塊架構圖:程序員
![image1](http://static.javashuo.com/static/loading.gif)
每一個模塊的簡單介紹以下:github
- Disconf-core
- 分佈式通知模塊:支持配置更新的實時化通知
- 路徑管理模塊:統一管理內部配置路徑URL
- Disconf-client
- 配置倉庫容器模塊:統一管理用戶實例中本地配置文件和配置項的內存數據存儲
- 配置reload模塊:監控本地配置文件的變更,並自動reload到指定bean
- 掃描模塊:支持掃描全部disconf註解的類和域
- 下載模塊:restful風格的下載配置文件和配置項
- watch模塊:監控遠程配置文件和配置項的變化
- 主備分配模塊:主備競爭結束後,統一管理主備分配與主備監控控制
- 主備競爭模塊:支持分佈式環境下的主備競爭
- Disconf-web
- 配置存儲模塊:管理全部配置的存儲和讀取
- 配置管理模塊:支持配置的上傳、下載、更新
- 通知模塊:當配置更新後,實時通知使用這些配置的全部實例
- 配置自檢監控模塊:自動定時校驗實例本地配置與中心配置是否一致
- 權限控制:web的簡單權限控制
- Disconf-tools
- context共享模塊:提供多實例間context的共享。
流程設計
![image2](http://static.javashuo.com/static/loading.gif)
運行流程詳細介紹:
與2.0版本的主要區別是支持了:主備分配功能/主備切換事件。
- 啓動事件A:如下按順序發生。
- A3:掃描靜態註解類數據,並注入到配置倉庫裏。
- A4+A2:根據倉庫裏的配置文件、配置項,去 disconf-web 平臺裏下載配置數據。這裏會有主備競爭
- A5:將下載獲得的配置數據值注入到倉庫裏。
- A6:根據倉庫裏的配置文件、配置項,去ZK上監控結點。
- A7+A2:根據XML配置定義,到 disconf-web 平臺裏下載配置文件,放在倉庫裏,並監控ZK結點。這裏會有主備競爭。
- A8:A1-A6均是處理靜態類數據。A7是處理動態類數據,包括:實例化配置的回調函數類;將配置的值注入到配置實體裏。
- 更新配置事件B:如下按順序發生。
- B1:管理員在 Disconf-web 平臺上更新配置。
- B2:Disconf-web 平臺發送配置更新消息給ZK指定的結點。
- B3:ZK通知 Disconf-cient 模塊。
- B4:與A4同樣。
- B5:與A5同樣。
- B6:基本與A4同樣,惟一的區別是,這裏還會將配置的新值注入到配置實體裏。
- 主備機切換事件C:如下按順序發生。
- C1:發生主機掛機事件。
- C2:ZK通知全部被影響到的備機。
- C4:與A2同樣。
- C5:與A4同樣。
- C6:與A5同樣。
- C7:與A6同樣。
模塊實現
disconf-web提供了先後端分離的web架構,具體可見:
https://github.com/knightliao/disconf/tree/master/disconf-web
本部分會重點介紹disconf-client的實現方式。
註解式disconf實現
本實現會涉及到 配置倉庫容器模塊、掃描模塊、下載模塊、watch模塊,
![http://ww1.sinaimg.cn/bmiddle/60c9620fjw1eqj9zzgc7yj20b20pn41v.jpg](http://static.javashuo.com/static/loading.gif)
使用AOP攔截的一個好處是能夠比較輕鬆的實現配置控制,好比並發環境下的配置統一輩子效。關於這方面的討論能夠見這裏。
特別地,本方式提供的編程模式很是簡單,例如使用如下配置類的程序在使用它時,能夠直接@Autowired進來進行調用,使用它時就和日常使用普通的JavaBean同樣,但其實它已經分佈式化了。配置更新時,配置類亦會自動更新。
@Service
@DisconfFile(filename = "redis.properties")
public class JedisConfig {
// 表明鏈接地址
private String host;
// 表明鏈接port
private int port;
/**
* 地址, 分佈式文件配置
*
* @return
*/
@DisconfFileItem(name = "redis.host", associateField = "host")
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
/**
* 端口, 分佈式文件配置
*
* @return
*/
@DisconfFileItem(name = "redis.port", associateField = "port")
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
基於XML配置disconf實現
本實現提供了無任何代碼侵入方式的分佈式配置。
ReloadablePropertiesFactoryBean繼承了Spring Properties文件的PropertiesFactoryBean類,管理全部當配置更新時要進行reload的配置文件。對於被管理的每個配置文件,都會經過 配置倉庫容器模塊、掃描模塊、下載模塊、watch模塊 進行配置獲取至配置倉庫裏。
ReloadingPropertyPlaceholderConfigurer繼承了Spring Bean配置值控制類PropertyPlaceholderConfigurer。在第一次掃描spring bean 時,disconf會記錄配置文件的配置與哪些bean有關聯。
ReloadConfigurationMonitor是一個定時任務,定時check本地配置文件是否有更新。
當配置中心的配置被更新時,配置文件會被下載至實例本地,ReloadConfigurationMonitor即會監控到此行爲,而且通知 ReloadingPropertyPlaceholderConfigurer 對相關的bean類進行值更新。
特別的,此種方式沒法解決併發狀況下配置統一輩子效的問題。
主備分配實現
在實現中,爲每一個配置提供主備選擇的概念。用戶實例在獲取配置前須要先進行全局惟一性競爭才能獲得配置值。在這裏,咱們採用基於zookeeper的全局惟一性鎖來實現。
1.5. Comparisons
|
淘寶Diamond[2] |
Disconf |
比較 |
數據持久性 |
存儲在mysql上 |
存儲在mysql上 |
都持久化到數據庫裏,都易於管理 |
推拉模型 |
拉模型,每隔15s拉一次全量數據 |
基於Zookeeper的推模型,實時推送 |
disconf基於分佈式的Zookeeper來實時推送,不斷是在穩定性、實效性、易用性上均優於diamond |
配置讀寫 |
支持實例對配置讀寫。支持某臺實例寫配置數據,並廣播到其它實例上 |
只支持實例對配置讀。經過在disconf-web上更新配置到達到廣播寫到全部應用實例 |
從目前的應用場景來看,實例對配置的寫需求不是那麼明顯。disconf支持的中心化廣播方案可能會與人性思考更加類似。 |
容災 |
多級容災模式,配置數據會dump在本地,避免中心服務掛機時沒法使用 |
多級容災模式,優先讀取本地配置文件。 |
雙方均支持在中心服務掛機時配置實例仍然可使用 |
配置數據模型 |
只支持KV結構的數據,非配置文件模式 |
支持傳統的配置文件模式(配置文件),亦支持KV結構數據(配置項) |
使用配置文件的編程方式可能與程序員的編程習慣更爲類似,更易於接受和使用。 |
編程模型 |
須要將配置文件拆成多個配置項,沒有明顯的編程模型 |
在使用配置文件的基礎上,提供了註解式和基於XML的兩種編程模型 |
無 |
併發性 |
多條配置要同時生效時,沒法解決併發同時生效的問題 |
基於註解式的配置,能夠解決併發性問題 |
無 |
1.6. Reference
- 王阿晶,鄒仕洪: 基於ZooKeeper的配置信息存儲方案的設計與實現
- 淘寶diamod實現:http://code.taobao.org/p/diamond/src/, 2012
- 百度BJF配置中心, 2014
- disconf github: https://github.com/knightliao/disconf, 2014
- 淘寶分佈式配置管理服務Diamond
- zooKeeper和Diamond有什麼不一樣
- diamond專題(一)– 簡介和快速使用
具體使用以下:
2.搭建項目
--就是一個disconf-web
2.1安裝依賴軟件
- 安裝Mysql(Ver 14.12 Distrib 5.0.45, for unknown-linux-gnu (x86_64) using EditLine wrapper)
- 安裝Tomcat(apache-tomcat-7.0.50)
- 安裝Nginx(nginx/1.5.3)
- 安裝 zookeeeper (zookeeper-3.3.0)
- 安裝 Redis (2.4.5)
2.2準備配置
將你的配置文件放到此地址目錄下(如下地址可自行設定):
home/work/dsp/disconf-rd/online-resources
配置文件包括:
- jdbc-mysql.properties (數據庫配置)
- redis-config.properties (Redis配置)
- zoo.properties (Zookeeper配置)
- application.properties (應用配置)
注意,記得執行將application-demo.properties複製成application.properties:
cp application-demo.properties application.properties
設置War包將要被部署的地址(如下地址可自行設定):
/home/work/dsp/disconf-rd/war
2.3構建
ONLINE_CONFIG_PATH=/home/work/dsp/disconf-rd/online-resources
WAR_ROOT_PATH=/home/work/dsp/disconf-rd/war
export ONLINE_CONFIG_PATH
export WAR_ROOT_PATH
cd disconf-web
sh deploy/deploy.sh
這樣會在 /home/work/dsp/disconf-rd/war 生成如下結果:
-disconf-web.war
-html
-META-INF
-WEB-INF
2.4上線前的初始化工做
初始化數據庫:
能夠參考 sql/readme.md 來進行數據庫的初始化。
裏面默認有6個用戶
若是想本身設置初始化的用戶名信息,能夠參考代碼來本身生成用戶:
src/main/java/com/baidu/disconf/web/tools/UserCreateTools.java
2.5部署War
修改server.xml文件,在Host結點下設定Context:
<Context path="" docBase="/home/work/dsp/disconf-rd/war"></Context>
並設置端口爲 8015
啓動Tomcat,便可。
2.6部署 前端
修改 nginx.conf
upstream disconf {
server 127.0.0.1:8015;
}
server {
listen 8081;
server_name localhost;
access_log /home/work/var/logs/disconf/access.log;
error_log /home/work/var/logs/disconf/error.log;
location / {
root /home/work/dsp/disconf-rd/war/html;
if ($query_string) {
expires max;
}
}
location ~ ^/(api|export) {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://disconf;
}
}
特別指出在打包項目時候請檢查python庫版本3.*版本會報錯,請用2.*打包
第二點,在打出來的war包修改諸如zookeeper的配置時,必須從新打包,單純修改war目錄下的文件是無效的
2.7使用
用戶名密碼 admin/admin
建立app,上傳配置
![](http://static.javashuo.com/static/loading.gif)
1.添加disconf.properties配置文件
disconf.user_define_download_dir=./ 此處意思就是將zookeeper中的配置拉到本地的classpath下,若配置成其餘目錄,彷佛會報錯
Java代碼
- disconf.enable.remote.conf=true
- disconf.conf_server_host=172.171.51.151:8082
- disconf.version=1.0.0.0
- disconf.app=17wifiServer
- disconf.env=local
- disconf.ignore=
- disconf.conf_server_url_retry_times=1
- disconf.conf_server_url_retry_sleep_seconds=1
- disconf.user_define_download_dir=./
- disconf.enable_local_download_dir_in_class_path=true
2.添加disconf.xml,而且指定給spring加載
配置以下
Java代碼
- <?xml version="1.0" encoding="UTF-8"?>
-
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
- <aop:aspectj-autoproxy proxy-target-class="true"/>
- <!-- 使用disconf必須添加如下配置 -->
- <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
- destroy-method="destroy">
- <property name="scanPackage" value="com.fnic.wifi.server"/>
- </bean>
- <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
- init-method="init" destroy-method="destroy">
- </bean>
- <!--################################################################ -->
- <!-- 使用託管方式的disconf配置(無代碼侵入, 配置更改會自動reload)-->
- <bean id="configproperties_disconf"
- class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean">
- <property name="locations">
- <list>
- <value>classpath:jdbc.properties</value>
- <value>classpath:redis.properties</value>
- </list>
- </property>
- </bean>
- <bean id="propertyConfigurer"
- class="com.baidu.disconf.client.addons.properties.ReloadingPropertyPlaceholderConfigurer">
- <property name="ignoreResourceNotFound" value="true"/>
- <property name="ignoreUnresolvablePlaceholders" value="true"/>
- <property name="propertiesArray">
- <list>
- <ref bean="configproperties_disconf"/>
- </list>
- </property>
- </bean>
- </beans>
配置上半段是必須配置,下半段是將配置文件託管給spring,而且能夠運用${}這樣的形式配置在spring的xml中,能夠參考jdbc數據源的配置
其中
Java代碼
- <value>classpath:jdbc.properties</value>
- <value>classpath:redis.properties</value>
- 要和上面
- disconf.user_define_download_dir=./ 對應起來
3.註釋掉spring本來的加載配置類
Java代碼
- <!-- <bean id="config"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath:conf/jdbc.properties</value>
- <value>classpath:conf/redis.properties</value>
- </list>
- </property>
- </bean> -->
4.編寫測試bean
Java代碼
- @Component
- @DisconfFile(filename = "configure.properties")
- public class ConfDIs {
- private String resourceUrl;
-
- @DisconfFileItem(name = "resource_server_url", associateField = "resourceUrl")
- public String getResourceUrl() {
- return resourceUrl;
- }
-
- public void setResourceUrl(String resourceUrl) {
- this.resourceUrl = resourceUrl;
- }
-
- }
ok啓動就能夠運用disconf了,其性能還有待驗證,不過其原理應該是從zk中拉配置到本地內存中,同本身手動加載本地的property文件。
正常的日誌以下
Java代碼
- 2016-04-27 17:57:27 [com.baidu.disconf.client.DisconfMgr]-[INFO] ******************************* DISCONF END FIRST SCAN *******************************
- 2016-04-27 17:57:28 [org.springframework.beans.GenericTypeAwarePropertyDescriptor]-[WARN] Invalid JavaBean property 'locations' being accessed! Ambiguous write methods found next to actually used [public void com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean.setLocations(java.util.List)]: [public void org.springframework.core.io.support.PropertiesLoaderSupport.setLocations(org.springframework.core.io.Resource[])]
- 2016-04-27 17:57:28 [com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean]-[INFO] Loading properties file from class path resource [jdbc.properties]
- 2016-04-27 17:57:28 [com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean]-[INFO] Loading properties file from class path resource [redis.properties]
- 2016-04-27 17:57:28 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[INFO] Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@63b7e77: defining beans [wifiAspect,activityDataBuilder,apActiveManageController,adDataBuilder,apAdController,apResourceController,resourceDataBuilder,apUserController,busController,busOnlineController,clickStatistics,confDIs,couponController,apFeedBackController,apFindAroundController,indexController,splashDataBuilder,messageSendController,sendMailController,pluginController,appScoreController,versionController,wifiAuthController,startListener,busServiceImpl,activityServiceImpl,apAdServiceImpl,apFeedBackSeriveImpl,appScoreServiceImpl,apResourceServiceImpl,apUserServiceImpl,busOnlineServiceImpl,couponServiceImpl,DAServiceImpl,findAroundServiceImpl,groupServiceImpl,indexServiceImpl,messageSendServiceImpl,pluginServiceImpl,versionServiceImpl,wifiAuthServiceImpl,httpService,springHolder,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,jedisPoolConfig,jedisConnFactory,stringRedisTemplate,objRedisTemplate,redisObj,redisString,redisTemplate,redisSubscribe,messageDelegateListener,serialization,messageListener,redisContainer,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,sqlSessionFactory,org.mybatis.spring.mapper.MapperScannerConfigurer#0,transactionManager,disconfMgrBean,disconfMgrBean2,configproperties_disconf,propertyConfigurer,disconfAspectJ,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,activityDao,apAdDao,apFeedBackDao,appScoreDao,apResourceDao,apUserDao,busDao,busOnlineDao,couponDao,DADao,findAroundDao,groupDao,indexDao,messageSendDao,pluginDao,versionDao,wifiAuthDao]; root of factory hierarchy
- 2016-04-27 17:57:28 [com.fnic.wifi.server.aop.WifiAspect]-[INFO] 初始化Controller Aop
- 2016-04-27 17:57:30 [com.baidu.disconf.client.DisconfMgr]-[INFO] ******************************* DISCONF START SECOND SCAN *******************************
- 2016-04-27 17:57:30 [com.baidu.disconf.client.DisconfMgr]-[INFO] Conf File Map:
- disconf-file: redis.properties
- DisconfCenterFile [
- keyMaps={}
- additionalKeyMaps={redis.maxWait=3000, redis.hostName=172.171.51.154, redis.maxIdle=5, redis.port=6380, redis.maxActive=500}
- cls=null
- remoteServerUrl=/api/config/file?app=17wifiServer&env=local&type=0&key=redis.properties&version=1.0.0.0]
- disconf-file: jdbc.properties
- DisconfCenterFile [
- keyMaps={}
- additionalKeyMaps={jdbc.url=jdbc:mysql://172.171.48.110:3306/wifimanage?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull, jdbc.minIdle=5, jdbc.username=root, jdbc.maxWait=3000, jdbc.removeAbandoned=true, jdbc.maxActive=15, jdbc.removeAbandonedTimeout=300, jdbc.maxIdle=30, jdbc.logAbandoned=true, jdbc.driver=com.mysql.jdbc.Driver, jdbc.password=root}
- cls=null
- remoteServerUrl=/api/config/file?app=17wifiServer&env=local&type=0&key=jdbc.properties&version=1.0.0.0]
- disconf-file: configure.properties
- DisconfCenterFile [
- keyMaps={resource_server_url=FileItemValue{value=http://172.171.51.151:8081/Resources2/, field=private java.lang.String com.fnic.wifi.server.controller.ConfDIs.resourceUrl, setMethod=null}}
- additionalKeyMaps={}
- cls=class com.fnic.wifi.server.controller.ConfDIs
- remoteServerUrl=/api/config/file?app=17wifiServer&env=local&type=0&key=configure.properties&version=1.0.0.0]
-
- 2016-04-27 17:57:30 [com.baidu.disconf.client.DisconfMgr]-[INFO] Conf Item Map:
-
- 2016-04-27 17:57:30 [com.baidu.disconf.client.DisconfMgr]-[INFO] ******************************* DISCONF END *******************************
3.參考
http://disconf.readthedocs.io/zh_CN/latest/