Dubbo基礎(三)- Provider的初始化過程B:外部化配置初始化過程

Provider初始化是一個較爲複雜,但是邏輯清晰的一段代碼。
上一篇文章細緻的分析了 refresh過程,refresh 是AbstractConfig的方法,即所有 它的子類,即所有 Config配置,都可以用到的方法。
主要功能有以下兩點:

  1. 讀取不同的配置
  2. 按照不同優先級設置配置到相應的Config

Dubbo可以配置不同的 註冊中心以及配置中心,那麼,是在哪些邏輯下面 適配不同配置中心的呢?
本篇文章將揭曉這一謎題。

前言 startConfigCenter

怎麼又是startConfigCenter?上篇文章不是分析過了?
由於在一開始例子中, 並沒有配置 配置中心,所以並不會執行:

            this.configCenter.refresh();
            prepareEnvironment();

上一句就是refresh過程,而讀取外部化配置則主要在 prepareEnvironment 中。

默認配置中心

上小節分析了,由於在第一篇例子中,分析到如果沒有配置中心,Dubbo 會給你設置默認配置中心。

還是接着 checkAndUpdateSubConfigs 往下。

checkDefault

checkDefault 主要是 實例化 ProviderConfig過程,如果 ProviderConfig 不存在,那麼就新建一個默認的,並執行 refresh 方法。

checkProtocol

checkProtocol 則主要是實例化 ProtocolConfig, 這裏會判斷是否有 providerConfig,如果有的化則會會設置爲 ProviderConfig 的配置,但是如果在ServiceConfig中自己有配置 ProtocolConfig 信息,則會使用 ServiceConfig 信息:

    private void checkProtocol() {
        if (CollectionUtils.isEmpty(protocols) && provider != null) {
            setProtocols(provider.getProtocols());
        }
     	// 如果ServiceConfig有,則進一步覆蓋
        convertProtocolIdsToProtocols();
    }

convertProtocolIdsToProtocols 中,會先去讀取 外部化配置 的所有的 protocol 信息,主要進行以下操作:

  1. 找不到任何ProtocolConfig, 則會新創鍵一個 ProtocolConfig ,但是這一步不回指定 Protocol 類型,後續會指定爲 dubbo
  2. 從外部化配置以及本實例中有配置 ProtocolConfig ,則會將其初始化並refresh,而後放入到 ServiceConfigtempProtocols

checkApplication

這一步則是檢查是否需要設定 ApplicationConfig 信息。

  1. 嘗試初始化 ApplicationConfig,最後會判斷是否有 name,通過判斷 name 來判斷,如果沒有,則會拋異常
  2. ApplicationModel 中保存 ApplicationConfigname
  3. 設置 SHUTDOWN_WAIT_SECONDS_KEYSHUTDOWN_WAIT_KEY 的值到 System.proterty中,這兩個屬性用於Dubbo優雅停機,相關研究後續文章中體現。

checkRegistry()

如果不是 injvm的應用,那麼就會調用檢查一遍 RegistryConfig,injvm特性主要是協議有且僅有一個,且使用的是 injvm

    protected void checkRegistry() {
    	// 從當前 ReferenceConfig中加載 config信息
        loadRegistriesFromBackwardConfig();
		// 讀取外部化配置的 
        convertRegistryIdsToRegistries();
		// 判斷是否有用
        for (RegistryConfig registryConfig : registries) {
            if (!registryConfig.isValid()) {
                throw new IllegalStateException("No registry config found or it's not a valid config! " +
                        "The registry config is: " + registryConfig);
            }
        }
		// 將讀取到的配置中心,進一步獲取,並
        useRegistryForConfigIfNecessary();
    }

上面代碼三個邏輯:

  1. 從當前 ReferenceConfig中加載 registries信息,並設置爲 RegistryConfig
  2. 如果上一步中,沒有發現任何registries信息,就會嘗試從 ** 外部化配置** 中獲取
// 判斷 registries 是否爲空
        if (StringUtils.isEmpty(registryIds) && CollectionUtils.isEmpty(registries)) {
            Set<String> configedRegistries = new HashSet<>();
            // 從外部化中獲取
            configedRegistries.addAll(getSubProperties(Environment.getInstance().getExternalConfigurationMap(),
                    REGISTRIES_SUFFIX));
            configedRegistries.addAll(getSubProperties(Environment.getInstance().getAppExternalConfigurationMap(),
                    REGISTRIES_SUFFIX));

            registryIds = String.join(COMMA_SEPARATOR, configedRegistries);
        }
  1. 依次判斷各個註冊中心是否有用,沒有用 即 !registryConfig.isValid() 則會拋出異常
  2. 嘗試從註冊中心中讀取配置,即將註冊中心作爲配置中心,並重新刷新整個配置讀取。
    將註冊中心變爲默認的配置中心,在 useRegistryForConfigIfNecessary 有所體現:
    private void useRegistryForConfigIfNecessary() {
        registries.stream().filter(RegistryConfig::isZookeeperProtocol).findFirst().ifPresent(rc -> {
            // we use the loading status of DynamicConfiguration to decide whether ConfigCenter has been initiated.
            Environment.getInstance().getDynamicConfiguration().orElseGet(() -> {
            // 獲取單例的 配置管理器
                ConfigManager configManager = ConfigManager.getInstance();
                // 窗即一個配置中心
                ConfigCenterConfig cc = configManager.getConfigCenter().orElse(new ConfigCenterConfig());
                // 設置協議
                cc.setProtocol(rc.getProtocol());
                // 設置地址
                cc.setAddress(rc.getAddress());
                // 設置優先級
                cc.setHighestPriority(false);
                // 設置配置中心
                setConfigCenter(cc);
                // 從配置中心刷新所有配置
                startConfigCenter();
                return null;
            });
        });
    }

這裏有一個細節,在將 registry 變爲默認的 ConfigCenter 時候,將優先級設爲false,這樣就不會讓這個配置中心去覆蓋本地的配置。

    // If the Config Center is given the highest priority, it will override all the other configurations
    private Boolean highestPriority = true;

prepareEnvironment

startConfigCenter 會有這麼一段:

       if (this.configCenter != null) {
            // TODO there may have duplicate refresh
            this.configCenter.refresh();
            prepareEnvironment();
        }

當配置中心不爲空時候,就會去執行 prepareEnvironment,這個方法主要就是讀取配置中心的配置。
主要有以下幾個作用:

  1. 通過SPI機制不同類型配置中心配置
  2. 讀取配置數據到Environment的 externalConfigurationMapappExternalConfigurationMap
        if (configCenter.isValid()) {
            if (!configCenter.checkOrUpdateInited()) {
                return;
            }
            // 加載 配置中心
            DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
            // 從配置中心獲取 configFile 配置
            String configContent = dynamicConfiguration.getConfigs(configCenter.getConfigFile(), configCenter.getGroup());

            String appGroup = application != null ? application.getName() : null;
            String appConfigContent = null;
            // 從配置中心獲取 appConfigFile 配置
            if (StringUtils.isNotEmpty(appGroup)) {
                appConfigContent = dynamicConfiguration.getConfigs
                        (StringUtils.isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
                         appGroup
                        );
            }
            try {
            // 將獲取到的String 類型的 url類型 數據轉化爲 key value
                Environment.getInstance().setConfigCenterFirst(configCenter.isHighestPriority());
                Environment.getInstance().updateExternalConfigurationMap(parseProperties(configContent));
                Environment.getInstance().updateAppExternalConfigurationMap(parseProperties(appConfigContent));
            } catch (IOException e) {
                throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
            }
        }

上述代碼中標識,如果ConfigCenter沒有appConfigFile,則會使用 configFile 替代 appConfigFile
最終,從配置文件中獲取到的,是 String類型,一行一個數據,最後用 Properties 再將 其讀出,執行
updateExternalConfigurationMap或者 updateAppExternalConfigurationMap 進行更新。

那麼往回看一下,dynamicConfiguration是怎麼加載配置的呢?

在 getDynamicConfiguration(URL url) 中:

		// SPI 方式,加載不同的 ConfigurationFactory
        DynamicConfigurationFactory factories = ExtensionLoader
                .getExtensionLoader(DynamicConfigurationFactory.class)
                .getExtension(url.getProtocol());
        // 獲取 動態 配置
        DynamicConfiguration configuration = factories.getDynamicConfiguration(url);
        Environment.getInstance().setDynamicConfiguration(configuration);
        return configuration;

上面即以 SPI 方式加載配置,根據 Protocol的類型,加載不同 擴展配置項。

得到配置以後,通過 getConfigs 讀取配置,通過傳入給定的 keygroup。 而前一行代碼中,相應的 動態配置類,從而執行不同的配置中心獲取邏輯:
在這裏插入圖片描述
目前dubbo(2.7.2)支持以下集中配置中心:

  1. Apollo:是攜程框架部門研發的開源配置管理中心 。Apollo GitHub 介紹
  2. Consul:是Spring Cloud的一款分佈式配置中心。Consul GitHub 介紹
  3. etcd:分佈式可靠的鍵值對存儲。etcd GitHub介紹
  4. Nacos:阿里巴巴 開源的一款分佈式註冊中心。 Nacos GitHub 介紹
  5. Zookeeper: Apache 一款開源的 配置中心。Zookeeper GitHub介紹

NopDynamicConfiguration 只是一個空架子,只是實現 DynamicConfiguration 但是沒有實現任何方法。以前是作爲默認註冊中心,即沒有聲明配置中心時候以它作爲默認配置中心。

checkMetadataReport

用於設置 MetadataReportConfig,嘗試從配置中加載其配置,就算 MetadataReportConfig 不可用(即 unvalid),也只會報警告。

餘下

餘下來的checkAndUpdateSubConfigs 有以下幾個點:

  1. 對是否爲 GenericService 進行適配(GenricService:泛化調用 是一種 Dubbo 提供的特殊角色,不需要引入jar包,直接通過GenericService進行調用,用於服務測試以及API服務網關,後面文章細分析)
  2. 檢查refinterface 是否爲同一個類別
  3. 檢查Stub和 Mock屬性,從而實現不同邏輯調用

以上幾個點需要單篇文章分析,希望關注後面文章。

總結

本文詳細講解了 Dubbo 的 服務提供者外部化配置初始化過程,主要包括以下:

  1. startConfigCenter中其他剩下的配置檢查以及加載過程
  2. SPI 方式配置中心加載(SPI 後面文章分析)
  3. 獲取到的加載的數據的設值

下一篇文章則進入 Provider初始化過程最後一個階段,服務暴露。是不是感覺終於要進入正題的感覺了??哈哈別急

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章