disconf原理 “入坑”指南

来源:https://www.cnblogs.com/xiangnanl/archive/2018/10/29/9865269.html
-Advertisement-
Play Games

之前有瞭解過disconf,也知道它是基於zookeeper來做的,但是對於其運行原理不太瞭解,趁著周末,debug下源碼,也算是不枉費周末大好時光哈 :) 。關於這篇文章,筆者主要是參考disconf源碼和官方文檔,若有不正確地方,感謝評論區指正交流~ disconf是一個分散式配置管理平臺(Di ...


之前有瞭解過disconf,也知道它是基於zookeeper來做的,但是對於其運行原理不太瞭解,趁著周末,debug下源碼,也算是不枉費周末大好時光哈 :) 。關於這篇文章,筆者主要是參考disconf源碼和官方文檔,若有不正確地方,感謝評論區指正交流~

disconf是一個分散式配置管理平臺(Distributed Configuration Management Platform),專註於各種 分散式系統配置管理 的通用組件/通用平臺, 提供統一的配置管理服務,是一套完整的基於zookeeper的分散式配置統一解決方案。disconf目前已經被多個公司在使用,包括百度、滴滴出行、銀聯、網易、拉勾網、蘇寧易購、順豐科技 等知名互聯網公司。disconf源碼地址 https://github.com/knightliao/disconf ,官方文檔 https://disconf.readthedocs.io/zh_CN/latest/

目前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端獲取最新配置並更新到本地。

 

disconf 功能特點:

  • 支持配置(配置項/配置文件)分散式管理
  • 配置發佈統一化
    • 配置發佈、更新統一化,同一個上線包 無須改動配置 即可在 多個環境中(RD/QA/PRODUCTION) 上線
    • 配置更新自動化:用戶在平臺更新配置,使用該配置的系統會自動發現該情況,並應用新配置。特殊地,如果用戶為此配置定義了回調函數類,則此函數類會被自動調用
  • 上手簡單,基於註解或者xml配置方式

功能特點描述圖

 

disconf 架構圖

 

分析disconf,最好是在本地搭建一個disconf-web環境,方便調試代碼,具體步驟可參考官方文檔,使用disconf-client,只需要在pom引入依賴即可:

<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。

 

1 disconf-client 初始化流程

關於disconf-client的初始化,聯想到Spring IoC流程,我們先不看代碼,可以猜想一下其大致流程,disconf-client首先需要從disconf服務端獲取配置,然後等到IoC流程中創建好對應的bean之後,將對應的配置值設置到bean中,這樣基本上就完成了初始化流程,其實disconf的初始化實現就是這樣的。   disconf-client的初始化開始於BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(Spring IoC初始化時,對於BeanDefinitionRegistryPostProcessor的實現類,會調用其postProcessBeanDefinitionRegistry方法),disconf的DisconfMgrBean類就是BeanDefinitionRegistryPostProcessor的實現類,DisconfMgrBean類的bean配置在哪裡呢?其實就是disconf.xml中的配置,該配置是必須的,示例如下:
<!-- 使用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屬性註入。

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);
}

 

1.1 firstScan

firstScan操作主要是載入系統配置和用戶配置(disconf.properties),進行包掃描併入庫,然後獲取獲取數據/註入/Watch。
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獲取某些符合條件的資源列表,如下:

/**
 * 掃描基本信息
 */
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;
}
View Code 獲取到資源信息(比如DisconfFile 和DisconfFileItem )之後,讀取DisConfFile類及其對應的DisconfFileItem信息,將它們放到disconfFileItemMap中,最後將這些信息存儲到倉庫DisconfCenterStore。這部分邏輯在ScanMgrImpl.firstScan方法中,整體邏輯還是比較清晰的,這裡就不貼代碼了。   掃描入庫之後,就該獲取數據/註入/Watch(DisconfCoreFactory.getDisconfCoreMgr()中邏輯)了。
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;
}
從disconf-web端獲取zk host和 zk prefix之後,會建立與zk的連接,然後就該從disconf-web端下載配置和watcher了,也就是disconfCoreMgr.process()邏輯。下載配置時disconf-client從disconf-web端獲取配置的全量數據(http連接),並存放到本地,然後解析數據,生成dataMap,dataMap是全量數據。然後將數據註入到倉庫中(DisconfCenterStore.confFileMap,類型為Map<String, DisconfCenterFile>)。註意:這裡還沒有將配置的值設置到bean中,設置bean值是在Spring的finishBeanFactoryInitialization流程做的,準確來說是在初始化bean DisconfMgrBeanSecond(bean配置在disconf.xml中),調用其init方法中做的。   在將值設置到倉庫之後,就該監聽對應配置了,這樣才能使用zk的watch機制,在zk上監聽的url格式為 /disconf/boot-demo_1_0_0_0_rd/file/specific.properties ,如果該url對應的node不存在則新建,註意該node是persistent類型的。然後在該node下新建臨時節點,節點名字是discon-client的簽名,格式為host_port_uuid,節點data為針對該配置文件,disconf-client需要的配置項的json格式數據,比如"{"port":9998,"host":"192.168.1.104"}"。  

1.2 註冊DisconfAspectJ

 往Spring中註冊一個aspect類DisconfAspectJ,該類會對@DisconfFileItem註解修飾的方法做切麵,功能就是當獲取bean屬性值時,如果開啟了DisClientConfig.getInstance().ENABLE_DISCONF,則返回disconf倉庫中對應的屬性值,否則返回bean實際值。註意:目前版本的disconf在更新倉庫中屬性值後會將bean的屬性值也一同更改,所以,目前DisconfAspectJ類作用已不大,不必理會,關於該類的討論可參考issue DisconfAspectJ 攔截的作用?

1.3 bean屬性註入

bean屬性註入是從DisconfMgr.secondScan開始的:

protected synchronized void secondScan() {
    // 掃描回調函數,也就是註解@DisconfUpdateService修飾的配置更新回調類,該類需實現IDisconfUpdate
    if (scanMgr != null) {
        scanMgr.secondScan();
    }

    // 註入數據至配置實體中
    if (disconfCoreMgr != null) {
        disconfCoreMgr.inject2DisconfInstance();
    }
}
  bean屬性註入通過獲取倉庫中對應的屬性值,然後調用setMethod.invoke或者field.set方法來設置,bean對應的boject是通過Spring來獲取的,也就是說,在獲取後bean已經初始化完成,只不過對應的屬性值還不是配置文件中配置的而已。如果程式中有2個類的@DisconfFile都是同一個配置文件,那麼這個時候獲取的bean是哪個類的bean呢?關於這個可以點擊issue DisconfFile用法咨詢,disconf目前只支持一個配置文件一個類的方式,不給兩個class同時使用同一個 "resources.properties",否則第二個是不生效的。

 

2 配置動態更新機制

disconf的配置動態更新藉助於zk的watch機制(watch機制是zk 3大重要內容之一,其餘兩個是zk協議和node存儲模型)實現的,初始化流程會中會對配置文件註冊watch,這樣當配置文件更新時,會通知到discnof-client,然後disconf-client再從disconf-web中獲取最新的配置並更新到本地,這樣就完成了配置動態更新。   配置動態更新動作開始於DisconfFileCoreProcessorImpl.updateOneConfAndCallback()方法:
/**
 * 更新消息: 某個配置文件 + 回調
 */
@Override
public void updateOneConfAndCallback(String key) throws Exception {

    // 更新 配置
    updateOneConf(key);

    // 回調
    DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);
    callUpdatePipeline(key);
}
更新配置時,首先更新倉庫中值,然後更新bean屬性值,配置更新回調是用戶自定義的回調方法,也就是@DisconfUpdateService修飾的類。配置更新時流程是: 開發人員在前端更新配置 -> disconf-web保存數據並更新zookeeper -> zookeeper通知disconf-client -> discnof-client 從 disconf-web下載對應配置 -> 更新倉庫和bean屬性 -> 調用回調 -> 更新配置完成。  

小結

disconf 作為一個分散式的配置管理平臺,文檔詳細,易於上手,動態配置更新,滿足大多數場景的配置更新需求。美中不足的是,代碼有的地方有點臃餘,獲取配置時還是全量獲取方式,目前還不支持多個類共用同一個 "resources.properties",多餘的DisconfAspectJ操作等。   使用disconf,最好的使用方式是基於它,將其改造成適合本公司或者項目組的工具,比如更方便的註解方式和回調方式等。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • java併發的一系列框架和技術主要是由java.util.concurrent 包所提供。包下的所有類可以分為如下幾大類: locks部分:顯式鎖(互斥鎖和速寫鎖)相關; atomic部分:原子變數類相關,是構建非阻塞演算法的基礎; executor部分:線程池相關; collections部分:併發 ...
  • 真值和假值 相等操作符(==和 ) 下麵分析一下不同類型的值用相等操作符(==)比較後的結果 toNumber 對不同 類型返回的結果如下: toPrimitive 對不同類型返回的結果如下: 操作符。如果比較的兩個值的類型相同,結果就如下;如果比較的兩個值類型不同,返回的就是false 下麵的例子 ...
  • 關於MVC架構中的Repository模式 關於MVC架構中的Repository模式 關於MVC架構中的Repository模式 關於MVC架構中的Repository模式 個人理解:Repository是一個獨立的層,介於領域層與數據映射層(數據訪問層)之間。它的存在讓領域層感覺不到數據訪問層的 ...
  • 本文長度為3032字,預計讀完需1.1MB流量,建議閱讀8分鐘。 閱讀目錄 為什麼沒有DNS? 如何實施? 優缺點 結語 閱讀目錄 為什麼沒有DNS? 如何實施? 優缺點 結語 為什麼沒有DNS? 如何實施? 優缺點 結語 為什麼沒有DNS? 如何實施? 優缺點 結語 為什麼沒有DNS? 如何實施? ...
  • 領域驅動的火爆程度不用我贅述,但是即便其如此得耳熟能詳,但大多數人對其的認識,還只是停留在知道它的縮寫是DDD,知道它是一種軟體思想,或者知道它和微服務有千絲萬縷的關係。Eric Evans對DDD的詮釋是那麼地惜字如金,而我所認識的領域驅動設計的專家又都是行業中的資深前輩,他們擅長於對軟體設計進行 ...
  • 第三篇這裡嘗試談談緩存的數據分片(Sharding)以及集群(Cluster)相關方案(具體應用依然以Redis 舉例)另見:分散式系統之緩存的微觀應用經驗談(二) 【主從和主備高可用篇】( https://www.cnblogs.com/bsfz/) 一、先分析緩存數據的分片(Sharding) ... ...
  • 前段時間分別用vue和react寫了兩個後臺管理系統的模板 "vue quasar admin" 和 "3YAdmin" 。兩個項目中都實現了基於RBAC的許可權控制。因為本職工作是後端開發,比較清楚許可權控制一個管理系統應該必須具備的核心功能,而且是可以做到通用的。打算寫寫關於管理系統前後端分離方面的 ...
  • File->settings->Editor->File and Code Templates->Python Script #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : ${DATE} ${TIME} # @Author : Ari ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...