Disconf源碼分析之啟動過程分析上(1)


Disconf的啟動,主要是包括兩次掃描和XML非注解式配置,總共分為上下兩篇,上篇先主要介紹第一次靜態掃描過程。

先從入口分析,通過Disconf幫助文檔,可以看到xml必須添加如下配置。

<!-- 使用disconf必須添加以下配置 -->
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
      destroy-method="destroy">
    <property name="scanPackage" value="com.demo.disconf"/>
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
      init-method="init" destroy-method="destroy">
</bean>

DisconfMgrBean繼承了ApplicationContextAware,disconf重載了postProcessBeanDefinitionRegistry()實現第一次加載。

/**
 * 第一次掃描<br/>
 * 在Spring內部的Bean定義初始化后執行,這樣是最高優先級的
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

    // 為了做兼容
    DisconfCenterHostFilesStore.getInstance().addJustHostFileSet(fileList);

    // 從2.6.23開始,disconf支持scanPackage傳遞多個包路徑,通過“,”分割,然后會切割包文件並去重存入到list中。
    List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN);
    // unique
    Set<String> hs = new HashSet<String>();
    hs.addAll(scanPackList);
    scanPackList.clear();
    scanPackList.addAll(hs);

    // 通過靜態提前加載的單例方式獲取DisconfMgr,並設置applicationContext上下文
    DisconfMgr.getInstance().setApplicationContext(applicationContext);
    // 進行掃描
    DisconfMgr.getInstance().firstScan(scanPackList);

    // register java bean
    registerAspect(registry);
}

看下DisconfMgr的firstScan()方法。

/**
 * 第一次掃描,靜態掃描 for annotation config
 */
protected synchronized void firstScan(List<String> scanPackageList) {

	// isFirstInit用來判斷第一次加載,結合synchronized加鎖來保證
    // 該函數不能調用兩次
    if (isFirstInit) {
        LOGGER.info("DisConfMgr has been init, ignore........");
        return;
    }

    try {
        // 導入配置
        ConfigMgr.init();
        // registry
        Registry registry = RegistryFactory.getSpringRegistry(applicationContext);

        // 掃描器
        scanMgr = ScanFactory.getScanMgr(registry);

        // 第一次掃描並入庫
        scanMgr.firstScan(scanPackageList);

        // 獲取數據/注入/Watch
        disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
        disconfCoreMgr.process();

        //
        isFirstInit = true
    } catch (Exception e) {
        LOGGER.error(e.toString(), e);
    }
}

先看ConfigMgr.init();的實現,disconf的配置文件包括系統自帶和用戶配置兩部分,分別由DisClientSysConfigDisClientConfig解析處理,也是單例實現。

// 導入系統配置
DisClientSysConfig.getInstance().loadConfig(null);
// 校驗 系統配置
DisInnerConfigHelper.verifySysConfig();
// 導入用戶配置
DisClientConfig.getInstance().loadConfig(null);
// 校驗 用戶配置
DisInnerConfigHelper.verifyUserConfig();
isInit = true;

先看DisClientSysConfig所有屬性都有注解@DisInnerConfigAnnotation,在后面的配置過程中,會通過反射的方式來賦值。

/**
 * STORE URL
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.conf_server_store_action")
public String CONF_SERVER_STORE_ACTION;

/**
 * STORE URL
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.conf_server_zoo_action")
public String CONF_SERVER_ZOO_ACTION;

/**
 * 獲取遠程主機個數的URL
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.conf_server_master_num_action")
public String CONF_SERVER_MASTER_NUM_ACTION;

/**
 * 下載文件夾, 遠程文件下載后會放在這里
 *
 * @author
 * @since 1.0.0
 */
@DisInnerConfigAnnotation(name = "disconf.local_download_dir")
public String LOCAL_DOWNLOAD_DIR;

DisClientSysConfig的loadConfig方法,會去調用DisconfAutowareConfig.autowareConfig()方法。默認的系統配置文件為disconf_sys.properties。

DisconfAutowareConfig主要的實現思路是通過DisconfAutowareConfig將配置的內容到導入到Properties中,然后通過反射的方式將上面DisClientSysConfig的各個屬性和配置對應賦值。

最后通過DisInnerConfigHelper.verifySysConfig()進行配置校驗。

DisClientConfig用戶配置的思路和系統配置相似,幾個不同點:

  1. DisClientConfig屬性不同
  2. 讀取的文件路徑,除了系統默認的disconf.properties,也可以通過啟動參數“-d”來指定配置文件。
  3. 配置文件讀取完成以后,還會通過讀取系統參數或命令行導入的屬性來覆蓋,優先使用該參數。

Config配置文件讀取完畢,繼續掃描工作。

// registry
// 將上下文處理成上下文處理工具
Registry registry = RegistryFactory.getSpringRegistry(applicationContext);

// 掃描器
// 通過掃描器工廠,獲取掃描器工具
scanMgr = ScanFactory.getScanMgr(registry);

// 第一次掃描並入庫
scanMgr.firstScan(scanPackageList);

// 獲取數據/注入/Watch
disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
disconfCoreMgr.process();

ScanFactory工廠得到的ScanMgrImpl構造函數有很多信息,ScanMgrImpl作為掃描模塊的中心。

public ScanMgrImpl(Registry registry) {
    this.registry = registry;
    // 配置文件
    staticScannerMgrList.add(StaticScannerMgrFactory.getDisconfFileStaticScanner());
    // 配置項
    staticScannerMgrList.add(StaticScannerMgrFactory.getDisconfItemStaticScanner());
    // 非注解 托管的配置文件
	staticScannerMgrList.add(StaticScannerMgrFactory.getDisconfNonAnnotationFileStaticScanner());
}

除了registry上下文的賦值,還包括將三種配置類型的掃描工具,載入到staticScannerMgrList中,在后面的掃描過程中,會遍歷工具分別處理Disconf上的配置。

開始掃描靜態文件,並且將結果存錯到store中。(DisconfCenterStore作為配置倉庫中心,后面會介紹)

/**
 * 掃描並存儲(靜態)
 */
public void firstScan(List<String> packageNameList) throws Exception {
    // 獲取掃描對象並分析整合
    scanModel = scanStaticStrategy.scan(packageNameList);
    // 增加非注解的配置
    scanModel.setJustHostFiles(DisconfCenterHostFilesStore.getInstance().getJustHostFiles());
    // 放進倉庫
    for (StaticScannerMgr scannerMgr : staticScannerMgrList) {
        // 掃描進入倉庫
        scannerMgr.scanData2Store(scanModel);
        // 忽略哪些KEY
        scannerMgr.exclude(DisClientConfig.getInstance().getIgnoreDisconfKeySet());
    }
}

ScanStaticModel的作用是配置掃描內容的存儲對象。

ReflectionScanStatic將路徑下文件掃描得到靜態注解,並整合到ScanStaticModel中。ReflectionScanStatic的主要處理方式是通過反射將Disconf支持的所有注解獲取到(具體的可以查看幫助文檔),初步掃描以后會進行解析,賦值到ScanStaticModel對象中。

獲取到靜態掃描的scanModel后,添加非注解的配置,這部分官方給出注釋說已經廢棄,只是兼容作用。

最后是對staticScannerMgrList的遍歷,通過各種類型的掃描工具,分別處理scanModel,將結果添加到DisconfCenterStore倉庫中。

看下掃描工具的實現,拿StaticScannerFileMgrImpl舉例。

繼承自StaticScannerMgr接口,實現了scanData2Store()和exclude()方法,scanData2Store()掃描數據並寫入倉庫,exclude()將Config指定的忽略配置內容從倉庫中移除。

@Override
public void scanData2Store(ScanStaticModel scanModel) {
    // 轉換配置文件
    List<DisconfCenterBaseModel> disconfCenterFiles = getDisconfFiles(scanModel);
    DisconfStoreProcessorFactory.getDisconfStoreFileProcessor().transformScanData(disconfCenterFiles);
}

首先會將scanModel轉回成List<DisconfCenterBaseModel>(File和Item的轉換是不同的,具體看源碼),文件配置是DisconfCenterFile,繼承自DisconfCenterBaseModel。然后依賴倉庫配置文件的處理工具DisconfStoreFileProcessorImpl,進行配置文件的處理和提交到倉庫。

看下倉庫工具的實現,拿DisconfStoreFileProcessorImpl舉例。

倉庫工具繼承了DisconfStoreProcessor接口,transformScanData()遍歷倉庫配置元素,調用DisconfCenterStore.getInstance()獲取單例DisconfCenterStore,調用storeOneFile()進行存儲配置。

/**
 * 存儲 一個配置文件
 */
public void storeOneFile(DisconfCenterBaseModel disconfCenterBaseModel) {
    DisconfCenterFile disconfCenterFile = (DisconfCenterFile) disconfCenterBaseModel;
    String fileName = disconfCenterFile.getFileName();
    if (confFileMap.containsKey(fileName)) {
        LOGGER.warn("There are two same fileName key!!!! " + fileName);
        DisconfCenterFile existCenterFile = confFileMap.get(fileName);
        // 如果是 同時使用了 注解式 和 非注解式 兩種方式,則當修改時也要 進行 XML 式 reload
        if (disconfCenterFile.isTaggedWithNonAnnotationFile()) {
        	// 該參數用於判斷是不是非注解式(托管式),設置為true,在配置更新時,會進行xml的reload
            existCenterFile.setIsTaggedWithNonAnnotationFile(true);
        }
    } else {
        confFileMap.put(fileName, disconfCenterFile);
    }
}

掃描工具StaticScannerItemMgrImpl和File的實現是相似的,同樣會有倉庫工具DisconfStoreItemProcessorImpl進行存儲。

對於非注解的掃描工具StaticScannerNonAnnotationFileMgrImpl,在當前的啟動過程中,不會存在該處理器需要處理的元素,后面介紹這個工具的使用。

繼續第一次掃描,目前為止已經將所有的注解配置都載入到倉庫中,倉庫中主要包含了哪些配置類、配置項等等,后面就可以根據這些元素,從Disconf服務端獲取配置值、注入到Bean,以及監聽配置更新並動態Reload。

// 獲取數據/注入/Watch
disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
disconfCoreMgr.process();

DisconfCoreFactory作為DisconfCoreMgr的工廠類,DisconfCoreMgr包含了FetcherMgr下載模塊和WatchMgrImpl模塊,主要依賴disconf-core中Zookeeper和Restful通用工具類。

DisconfCoreMgrImpl是核心處理器,構造函數進行賦值。

private List<DisconfCoreProcessor> disconfCoreProcessorList = new ArrayList<DisconfCoreProcessor>();
// 監控器
private WatchMgr watchMgr = null;
// 抓取器
private FetcherMgr fetcherMgr = null;
// registry
private Registry registry = null;
public DisconfCoreMgrImpl(WatchMgr watchMgr, FetcherMgr fetcherMgr, Registry registry) {
    this.watchMgr = watchMgr;
    this.fetcherMgr = fetcherMgr;
    this.registry = registry;
    //在這里添加好配置項、配置文件的處理器
    DisconfCoreProcessor disconfCoreProcessorFile = DisconfCoreProcessorFactory.getDisconfCoreProcessorFile(watchMgr, fetcherMgr, registry);
    disconfCoreProcessorList.add(disconfCoreProcessorFile);
    DisconfCoreProcessor disconfCoreProcessorItem = DisconfCoreProcessorFactory.getDisconfCoreProcessorItem(watchMgr, fetcherMgr, registry);
    disconfCoreProcessorList.add(disconfCoreProcessorItem);
}

disconfCoreProcessorList包含了DisconfFileCoreProcessorImplDisconfItemCoreProcessorImpl的處理器,這一步設計和前面File和Item的Scan掃描處理類似,處理器實現了DisconfCoreProcessor接口 。

disconfCoreMgr.process();遍歷兩種處理器,調用processAllItems()處理。看處理器的具體任務,DisconfFileCoreProcessorImpl舉例說明。

@Override
public void processAllItems() {

    /**
     * 配置文件列表處理
     * disconfStoreProcessor具體實現是DisconfStoreFileProcessorImpl
     */
    for (String fileName : disconfStoreProcessor.getConfKeySet()) {
        // 獲取所有的配置文件名
        processOneItem(fileName);
    }
}

@Override
public void processOneItem(String key) {
    // 獲取倉庫中的元素
    DisconfCenterFile disconfCenterFile = (DisconfCenterFile) disconfStoreProcessor.getConfData(key);
    try {
        updateOneConfFile(key, disconfCenterFile);
    } catch (Exception e) {
        LOGGER.error(e.toString(), e);
    }
}

/**
 * 更新 一個配置文件, 下載、注入到倉庫、Watch 三步驟
 */
private void updateOneConfFile(String fileName, DisconfCenterFile disconfCenterFile) throws Exception {

    if (disconfCenterFile == null) {
        throw new Exception("cannot find disconfCenterFile " + fileName);
    }

    String filePath = fileName;
    Map<String, Object> dataMap = new HashMap<String, Object>();

    //
    // 開啟disconf才需要遠程下載, 否則就本地就好
    //
    if (DisClientConfig.getInstance().ENABLE_DISCONF) {

        //
        // 下載配置
        //
        try {
            // 先獲取配置的路徑,在載入時可以看到。
            String url = disconfCenterFile.getRemoteServerUrl();
            // 該方法主要是通過url進行文檔下載,具體里面的實現,會將配置文件下載並移動都指定路徑。
            filePath = fetcherMgr.downloadFileFromServer(url, fileName, disconfCenterFile.getFileDir());

        } catch (Exception e) {

            //
            // 下載失敗了, 嘗試使用本地的配置
            //
            LOGGER.error(e.toString(), e);
            LOGGER.warn("using local properties in class path: " + fileName);

            // change file path
            filePath = fileName;
        }
        LOGGER.debug("download ok.");
    }

    try {
        // FileTypeProcessorUtils配置處理器,根據配置項的類型和文件路徑,讀取配置值到Map,總共有*、xml、properties,目前只有對properties類型會做解析,返回Map。
        dataMap = FileTypeProcessorUtils.getKvMap(disconfCenterFile.getSupportFileTypeEnum(),
                disconfCenterFile.getFilePath());
    } catch (Exception e) {
        LOGGER.error("cannot get kv data for " + filePath, e);
    }

    //
    // 注入到倉庫中
    //
    disconfStoreProcessor.inject2Store(fileName, new DisconfValue(null, dataMap));
    LOGGER.debug("inject ok.");

    //
    // 開啟disconf才需要進行watch
    //
    if (DisClientConfig.getInstance().ENABLE_DISCONF) {
        //
        // Watch
        //
        DisConfCommonModel disConfCommonModel = disconfStoreProcessor.getCommonModel(fileName);
        if (watchMgr != null) {
            watchMgr.watchPath(this, disConfCommonModel, fileName, DisConfigTypeEnum.FILE,
                    GsonUtils.toJson(disconfCenterFile.getKV()));
            LOGGER.debug("watch ok.");
        } else {
            LOGGER.warn("cannot monitor {} because watch mgr is null", fileName);
        }
    }
}

看下inject2Store()的處理:

public void inject2Store(String fileName, DisconfValue disconfValue) {

    // 獲取配置中心對象
    DisconfCenterFile disconfCenterFile = getInstance().getConfFileMap().get(fileName);
    // 校驗是否存在
    if (disconfCenterFile == null) {
        LOGGER.error("cannot find " + fileName + " in store....");
        return;
    }
    if (disconfValue == null || disconfValue.getFileData() == null) {
        LOGGER.error("value is null for {}", fileName);
        return;
    }
    // 存儲、將dataMap的值存儲到對象的屬性上
    Map<String, FileItemValue> keMap = disconfCenterFile.getKeyMaps();
    if (keMap.size() > 0) {
        for (String fileItem : keMap.keySet()) {

            Object object = disconfValue.getFileData().get(fileItem);
            if (object == null) {
                LOGGER.error("cannot find {} to be injected. file content is: {}", fileItem,
                        disconfValue.getFileData().toString());
                continue;
            }

            // 根據類型設置值
            try {

                Object value = keMap.get(fileItem).getFieldValueByType(object);
                keMap.get(fileItem).setValue(value);

            } catch (Exception e) {
                LOGGER.error("inject2Store filename: " + fileName + " " + e.toString(), e);
            }
        }
    }
    // 使用過 XML式配置
    if (disconfCenterFile.isTaggedWithNonAnnotationFile()) {
        if (disconfCenterFile.getSupportFileTypeEnum().equals(SupportFileTypeEnum.PROPERTIES)) {
            // 如果是采用XML進行配置的,則需要利用spring的reload將數據reload到bean里
            ReloadConfigurationMonitor.reload();
        }
        disconfCenterFile.setAdditionalKeyMaps(disconfValue.getFileData());
    }
}

還是比較清晰的,會判斷倉庫中存儲對象的狀態,然后將相應的對象值進行存儲。下面對於XML式配置的處理,暫時不做介紹,后面介紹。

倉庫中的配置處理完成以后,如果需要監聽配置文件的更新,那么就需要通過Watch去做監控,監控的目標主體是由配置app名、版本號、env環境構成的對象DisConfCommonModel以及其他信息。disConfCommonModel在scanData2Store()中轉換配置文件的時候已經存儲到倉庫對象中。

下面看下最后一行代碼:

watchMgr.watchPath(this, disConfCommonModel, fileName, DisConfigTypeEnum.FILE,
                    GsonUtils.toJson(disconfCenterFile.getKV()));

我們主要看前面兩個參數,第二個是剛剛說的監控對象,第一個參數是this自身。

public void watchPath(DisconfCoreProcessor disconfCoreMgr, DisConfCommonModel disConfCommonModel, String keyName,
                          DisConfigTypeEnum disConfigTypeEnum, String value) throws Exception {
        // 新建
        String monitorPath = makeMonitorPath(disConfigTypeEnum, disConfCommonModel, keyName, value);
        // 進行監控
        NodeWatcher nodeWatcher =
                new NodeWatcher(disconfCoreMgr, monitorPath, keyName, disConfigTypeEnum, new DisconfSysUpdateCallback(),
                        debug);
        nodeWatcher.monitorMaster();
    }

因為分布式一致性是通過zookeeper實現的,節點模型需要將disConfCommonModel對象和其他信息轉換成目錄路徑monitorPath。

NodeWatcher是zookeeper api接口Watcher的實現,可以看到disconfCoreMgr會被設置為NodeWatcher屬性,當Watcher在配置更新監控到消息,執行監聽代碼是,會調用new DisconfSysUpdateCallback()的reload()方法,而reload()方法會調用disconfCoreMgr的updateOneConfAndCallback()方法。具體的回調操作后面介紹。

以上是DisconfFileCoreProcessorImpl的實現邏輯,DisconfFItemCoreProcessorImpl的邏輯也是類似的。

回到最開始第一次靜態掃描入口,最后剩下bean注入。

registerAspect(registry);

手動注入DisconfAspectJ.class對象。通過AOP攔截方式,用於獲取配置文件和配置項。

至此第一次靜態掃描工作結束,分析過程有一些簡單的代碼實現和跟蹤,沒有詳細的解說,可以自行查看源碼。

轉載請注明出處。
作者:wuxiwei
出處:https://www.cnblogs.com/wxw16/p/10701673.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM