以前有了解過disconf,也知道它是基於zookeeper來作的,可是對於其運行原理不太瞭解,趁着週末,debug下源碼,也算是不枉費週末大好時光哈 :) 。關於這篇文章,筆者主要是參考disconf源碼和官方文檔,如有不正確地方,感謝評論區指正交流~前端
disconf是一個分佈式配置管理平臺(Distributed Configuration Management Platform),專一於各類 分佈式系統配置管理 的通用組件/通用平臺, 提供統一的配置管理服務,是一套完整的基於zookeeper的分佈式配置統一解決方案。disconf目前已經被多個公司在使用,包括百度、滴滴出行、銀聯、網易、拉勾網、蘇寧易購、順豐科技 等知名互聯網公司。disconf源碼地址 https://github.com/knightliao/disconf ,官方文檔 https://disconf.readthedocs.io/zh_CN/latest/ 。java
目前disconf包含了 客戶端disconf-Client和 管理端disconf-Web兩個模塊,均由java實現。服務依賴組件包括Nginx、Tomcat、Mysql、ZooKeeper,nginx提供反向代理(disconf-web是先後端分離的),Tomcat是後端web容器,配置存儲在mysql上,基於zookeeper的wartch模型,實時推送。注意,disconf優先讀取本地文件,disconf只支持應用對配置的讀操做,經過在disconf-web上更新配置,而後由zookeeper通知到服務實例,最後服務實例去disconf-web端獲取最新配置並更新到本地。node
disconf 功能特色:mysql
功能特色描述圖nginx
disconf 架構圖git
分析disconf,最好是在本地搭建一個disconf-web環境,方便調試代碼,具體步驟可參考官方文檔,使用disconf-client,只須要在pom引入依賴便可:github
<dependency> <groupId>com.baidu.disconf</groupId> <artifactId>disconf-client</artifactId> <version>2.6.36</version> </dependency>
對於開發人員來講,最多接觸的就是disconf-web配置和disconf-client了,disconf-web配置官方文檔已經很詳細了,這裏就來不及解釋了,抓緊上車,去分析disconf-client的實現,disconf-client最重要的內容就是disconf-client初始化流程和配置動態更新機制。disconf的功能是基於Spring的(初始化是在Spring的BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry開始的,配置動態更新也是要更新到Spring IoC中對應的bean),因此使用disconf,項目必須基於Spring。web
<!-- 使用disconf必須添加如下配置 --> <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean" destroy-method="destroy"> <property name="scanPackage" value="com.luo.demo"/> </bean> <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond" init-method="init" destroy-method="destroy"> </bean>
DisconfMgrBean#postProcessBeanDefinitionRegistry方法主要作的3件事就是掃描(firstScan)、註冊DisconfAspectJ 和 bean屬性注入。sql
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // scanPackList包括disconf.xml中DisconfMgrBean.scanPackage List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN); // 1. 進行掃描 DisconfMgr.getInstance().setApplicationContext(applicationContext); DisconfMgr.getInstance().firstScan(scanPackList); // 2. register java bean registerAspect(registry); }
protected synchronized void firstScan(List<String> scanPackageList) { // 導入配置 ConfigMgr.init(); // registry Registry registry = RegistryFactory.getSpringRegistry(applicationContext); // 掃描器 scanMgr = ScanFactory.getScanMgr(registry); // 第一次掃描併入庫 scanMgr.firstScan(scanPackageList); // 獲取數據/注入/Watch disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry); disconfCoreMgr.process(); }
進行包掃描是使用Reflections來完成的,獲取路徑下(好比xxx/target/classes)某個包下符合條件(好比com.luo.demo)的資源(reflections),而後從reflections獲取某些符合條件的資源列表,以下:json
/** * 掃描基本信息 */ private ScanStaticModel scanBasicInfo(List<String> packNameList) { ScanStaticModel scanModel = new ScanStaticModel(); // 掃描對象 Reflections reflections = getReflection(packNameList); scanModel.setReflections(reflections); // 獲取DisconfFile class Set<Class<?>> classdata = reflections.getTypesAnnotatedWith(DisconfFile.class); scanModel.setDisconfFileClassSet(classdata); // 獲取DisconfFileItem method Set<Method> af1 = reflections.getMethodsAnnotatedWith(DisconfFileItem.class); scanModel.setDisconfFileItemMethodSet(af1); // 獲取DisconfItem method af1 = reflections.getMethodsAnnotatedWith(DisconfItem.class); scanModel.setDisconfItemMethodSet(af1); // 獲取DisconfActiveBackupService classdata = reflections.getTypesAnnotatedWith(DisconfActiveBackupService.class); scanModel.setDisconfActiveBackupServiceClassSet(classdata); // 獲取DisconfUpdateService classdata = reflections.getTypesAnnotatedWith(DisconfUpdateService.class); scanModel.setDisconfUpdateService(classdata); return scanModel; }
public static DisconfCoreMgr getDisconfCoreMgr(Registry registry) throws Exception { FetcherMgr fetcherMgr = FetcherFactory.getFetcherMgr(); // 不開啓disconf,則不要watch了 WatchMgr watchMgr = null; if (DisClientConfig.getInstance().ENABLE_DISCONF) { // Watch 模塊 watchMgr = WatchFactory.getWatchMgr(fetcherMgr); } return new DisconfCoreMgrImpl(watchMgr, fetcherMgr, registry); } public static WatchMgr getWatchMgr(FetcherMgr fetcherMgr) throws Exception { synchronized(hostsSync) { // 從disconf-web端獲取 Zoo Hosts信息,及zookeeper host和zk prefix信息(默認 /disconf) hosts = fetcherMgr.getValueFromServer(DisconfWebPathMgr.getZooHostsUrl(DisClientSysConfig .getInstance() .CONF_SERVER_ZOO_ACTION)); zooPrefix = fetcherMgr.getValueFromServer(DisconfWebPathMgr.getZooPrefixUrl(DisClientSysConfig .getInstance () .CONF_SERVER_ZOO_ACTION)); /** * 初始化watchMgr,這裏會與zookeeper創建鏈接,若是/disconf節點不存在會新建 */ WatchMgr watchMgr = new WatchMgrImpl(); watchMgr.init(hosts, zooPrefix, DisClientConfig.getInstance().DEBUG); return watchMgr; } return null; }
往Spring中註冊一個aspect類DisconfAspectJ,該類會對@DisconfFileItem註解修飾的方法作切面,功能就是當獲取bean屬性值時,若是開啓了DisClientConfig.getInstance().ENABLE_DISCONF,則返回disconf倉庫中對應的屬性值,不然返回bean實際值。注意:目前版本的disconf在更新倉庫中屬性值後會將bean的屬性值也一同更改,因此,目前DisconfAspectJ類做用已不大,沒必要理會,關於該類的討論可參考issue DisconfAspectJ 攔截的做用?
bean屬性注入是從DisconfMgr.secondScan開始的:
protected synchronized void secondScan() { // 掃描回調函數,也就是註解@DisconfUpdateService修飾的配置更新回調類,該類需實現IDisconfUpdate if (scanMgr != null) { scanMgr.secondScan(); } // 注入數據至配置實體中 if (disconfCoreMgr != null) { disconfCoreMgr.inject2DisconfInstance(); } }
/** * 更新消息: 某個配置文件 + 回調 */ @Override public void updateOneConfAndCallback(String key) throws Exception { // 更新 配置 updateOneConf(key); // 回調 DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key); callUpdatePipeline(key); }