應用配置管理,基礎原理分析

知了一笑發表於2022-06-21

工程可以有點小亂,但配置不能含糊;

一、配置架構

在微服務的程式碼工程中,配置管理是一項複雜的事情,即需要做好各個環境的配置隔離措施,還需要確保生產環境的配置安全;如果劃分的微服務足夠的多,還要考慮配置更新時的效率;

常規情況下,在配置管理的體系中,分為四個主要的環境:開發、測試、灰度、生產;通常來說除了運維團隊之外,其他人員沒有檢視灰度和生產配置的許可權,以此來保證配置的安全性;配置中心的服務也會搭建兩套:研發與生產各自獨立部署。

二、配置方式

在專案中涉及到的配置非常多,型別也很多,但是從大的結構上可以分為:工程級、應用級、元件級三大塊;實際上這裡只是單純的從程式碼工程來看配置,如果把持續整合、自動化相關模組也考慮進來的話,配置的管理會更加複雜;

站在開發的角度來看,主要還是對應用級的配置進行管理,而在微服務的架構下,多個不同的服務既有大量相同的配置,又存在各種差異化的自定義引數,所以在維護上有一定的複雜性;

在單服務的工程中,應用中只會存在一個bootstrap.yml配置檔案,配置內容基本就是服務名稱,和配置中心地址等常規內容,其他複雜的配置都被封閉維護,避免核心內容洩露引發安全問題;

配置主體通常會被拆分成如下幾個層次:環境控制,用來識別灰度和生產;應用基礎,管理和載入各個服務的通用配置;服務差異則配置在各自獨立的檔案內;並且對多個配置進行分類分層管理;以此保證配置的安全和降低維護難度。

三、Nacos配置

首先還是從bootstrap.yml檔案作為切入點,以當下常見的Nacos元件為例,圍繞基礎原理作為思路,來分析服務工程是如何載入Nacos配置中心的引數;

spring:
  profiles:
    active: dev,facade
  cloud:
    nacos:
      config:
        prefix: application
        server-addr: 127.0.0.1:8848
        file-extension: yml

元件配置:配置邏輯中,單個服務端提供自身配置引數的資訊,從上篇服務管理的原始碼中發現,這是一種很常用的手段;需要基於這些資訊去Nacos服務中載入配置;

@ConfigurationProperties("spring.cloud.nacos.config")
public class NacosConfigProperties {
    public Properties assembleConfigServiceProperties() {
        Properties properties = new Properties();
        properties.put("serverAddr", Objects.toString(this.serverAddr, ""));
        properties.put("namespace", Objects.toString(this.namespace, ""));
        return properties ;
    }
}

載入邏輯:服務啟動時,先基於相應引數讀取Nacos中配置的,然後解析資料並被Spring框架進行載入,載入的過程通過MapPropertySource類進行Key和Value的讀取;

public class NacosPropertySourceBuilder {
    private List<PropertySource<?>> loadNacosData(String dataId, String group, String fileExtension) {
        // 查詢配置
        String data = this.configService.getConfig(dataId, group, this.timeout);
        // 解析配置
        return NacosDataParserHandler.getInstance().parseNacosData(dataId, data, fileExtension);
    }
}

請求服務:服務端與Nacos中心通過Http請求的方式進行互動,通過Get請求攜帶引數,呼叫Nacos中心服務,從而獲取相應配置;

public class ClientWorker implements Closeable {
    public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout) {
        // 核心引數
        Map<String, String> params = new HashMap<String, String>(3);
        params.put("dataId", dataId);
        params.put("group", group);
        params.put("tenant", tenant);
        // 執行請求
        HttpRestResult<String> result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
    }
}

四、Spring載入

從原始碼的結構圖來看,配置檔案的載入邏輯也是採用事件模型實現的,這在前面任務管理中有詳細的描述;配置的核心作用就是在程式啟動時進行載入引導,這裡關鍵要理解EnvironmentPostProcessor的介面設計;

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    // 基礎配置
    private static final String DEFAULT_NAMES = "application";
    public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
    public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
    static {
	    Set<String> filteredProperties = new HashSet<>();
	    filteredProperties.add("spring.profiles.active");
	    filteredProperties.add("spring.profiles.include");
	    LOAD_FILTERED_PROPERTY = Collections.unmodifiableSet(filteredProperties);
    }
    // 載入邏輯
    void load() {
	FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
			(defaultProperties) -> {
			});
    }
}

EnvironmentPostProcessor介面:載入應用的自定義環境;

@FunctionalInterface
public interface EnvironmentPostProcessor {
	void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}

SpringApplication:用於啟動和引導應用程式,提供建立程式上下文例項,初始化監聽器,容器重新整理等核心邏輯,可以圍繞run()方法進行除錯分析;

ConfigurableEnvironment:環境配置的核心介面,涉及到當前配置檔案識別,即profiles.active;以及配置檔案的解析能力,即PropertyResolver;在Loader內部類中提供了構造方法和載入邏輯的實現。

五、參考原始碼

應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent

元件封裝:
https://gitee.com/cicadasmile/butte-frame-parent

相關文章