Seata 配置中心實現原理
- 2019 年 12 月 16 日
- 筆記
Seata 可以支援多個第三方配置中心,那麼 Seata 是如何同時兼容那麼多個配置中心的呢?下面我給大家詳細介紹下 Seata 配置中心的實現原理。
配置中心屬性載入
在 Seata 配置中心,有兩個默認的配置文件:

file.conf 是默認的配置屬性,registry.conf 主要存儲第三方註冊中心與配置中心的資訊,主要有兩大塊:
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa # ... } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "localhost" namespace = "" } file { name = "file.conf" } # ... }
其中 registry 為註冊中心的配置屬性,這裡先不講,config 為配置中心的屬性值,默認為 file 類型,即會載入本地的 file.conf 裡面的屬性,如果 type 為其它類型,那麼會從第三方配置中心載入配置屬性值。
在 config 模組的 core 目錄中,有個配置工廠類 ConfigurationFactory,它的結構如下:

可以看到都是一些配置的靜態常量:
REGISTRY_CONF_PREFIX、REGISTRY_CONF_SUFFIX:配置文件名、默認配置文件類型;
SYSTEM_PROPERTY_SEATA_CONFIG_NAME、ENV_SEATA_CONFIG_NAME、ENV_SYSTEM_KEY、ENV_PROPERTY_KEY:自定義文件名配置變數,也說明我們可以自定義配置中心的屬性文件。
ConfigurationFactory 裡面有一處靜態程式碼塊,如下:
io.seata.config.ConfigurationFactory

根據自定義文件名配置變數找出配置文件名稱與類型,如果沒有配置,默認使用 registry.conf,FileConfiguration 是 Seata 默認的配置實現類,如果為默認值,則會更具 registry.conf 配置文件生成 FileConfiguration 默認配置對象,這裡也可以利用 SPI 機制支援第三方擴展配置實現,具體實現是繼承 ExtConfigurationProvider 介面,在META-INF/services/
創建一個文件並填寫實現類的全路徑名,如下所示:

第三方配置中心實現類載入
在靜態程式碼塊邏輯載入完配置中心屬性之後,Seata 是如何選擇配置中心並獲取配置中心的屬性值的呢?
我們剛剛也說了 FileConfiguration 是 Seata 的默認配置實現類,它繼承了 AbstractConfiguration,它的基類為 Configuration,提供了獲取參數值的方法:
short getShort(String dataId, int defaultValue, long timeoutMills); int getInt(String dataId, int defaultValue, long timeoutMills); long getLong(String dataId, long defaultValue, long timeoutMills); // ....
那麼意味著只需要第三方配置中心實現該介面,就可以整合到 Seata 配置中心了,下面我拿 zk 來做例子:
首先,第三方配置中心需要實現一個 Provider 類:

實現的 provider 方法如其名,主要是輸出具體的 Configuration 實現類。
那麼我們是如何獲取根據配置去獲取對應的第三方配置中心實現類呢?
在 Seata 項目中,獲取一個第三方配置中心實現類通常是這麼做的:
Configuration CONFIG = ConfigurationFactory.getInstance();
在 getInstance() 方法中主要是使用了單例模式構造配置實現類,它的構造具體實現如下:
io.seata.config.ConfigurationFactory#buildConfiguration:

首先從 ConfigurationFactory 中的靜態程式碼塊根據 registry.conf 創建的 CURRENT_FILE_INSTANCE 中獲取當前環境使用的配置中心,默認為為 File 類型,我們也可以在 registry.conf 配置其它第三方配置中心,這裡也是利用了 SPI 機制去載入第三方配置中心的實現類,具體實現如下:

如上,即是剛剛我所說的 ZookeeperConfigurationProvider 配置實現輸出類,我們再來看看這行程式碼:
EnhancedServiceLoader.load(ConfigurationProvider.class,Objects.requireNonNull(configType).name()).provide();
EnhancedServiceLoader 是 Seata SPI 實現核心類,這行程式碼會載入 META-INF/services/
和 META-INF/seata/
目錄中文件填寫的類名,那麼如果其中有多個配置中心實現類都被載入了怎麼辦呢?
我們注意到 ZookeeperConfigurationProvider 類的上面有一個註解:
@LoadLevel(name = "ZK", order = 1)
在載入多個配置中心實現類時,會根據 order 進行排序:
io.seata.common.loader.EnhancedServiceLoader#findAllExtensionClass:

io.seata.common.loader.EnhancedServiceLoader#loadFile:

這樣,就不會產生衝突了。
但是我們發現 Seata 還可以用這個方法進行選擇,Seata 在調用 load 方法時,還傳了一個參數:
Objects.requireNonNull(configType).name()
ConfigType 為配置中心類型,是個枚舉類:
public enum ConfigType { File, ZK, Nacos, Apollo, Consul, Etcd3, SpringCloudConfig, Custom; }
我們注意到,LoadLevel 註解上還有一個 name 屬性,在進行篩選實現類時,Seata 還做了這個操作:

根據當前 configType 來判斷是否等於 LoadLevel 的 name 屬性,如果相等,那麼就是當前配置的第三方配置中心實現類。
第三方配置中心實現類
ZookeeperConfiguration 繼承了 AbstractConfiguration,它的構造方法如下:

構造方法創建了一個 zkClient 對象,這裡的 FILE_CONFIG 是什麼呢?
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
原來就是剛剛靜態程式碼塊中創建的 registry.conf 配置實現類,從該配置實現類拿到第三方配置中心的相關屬性,構造第三方配置中心客戶端,然後實現 Configuration 介面時:

就可以利用客戶端相關方法去第三方配置獲取對應的參數值了。
第三方配置中心同步腳本
上周末才寫好,已經提交 PR 上去了,還處於 review 中,預估會在 Seata 1.0 版本提供給大家使用,敬請期待。
具體位置在 Seata 項目的 script 目錄中:

config.txt 為本地配置好的值,搭建好第三方配置中心之後,運行腳本會將 config.txt 的配置同步到第三方配置中心。