工程可以有點小亂,但配置不能含糊;
一、配置架構
在微服務的程式碼工程中,配置管理是一項複雜的事情,即需要做好各個環境的配置隔離措施,還需要確保生產環境的配置安全;如果劃分的微服務足夠的多,還要考慮配置更新時的效率;
常規情況下,在配置管理的體系中,分為四個主要的環境:開發、測試、灰度、生產;通常來說除了運維團隊之外,其他人員沒有檢視灰度和生產配置的許可權,以此來保證配置的安全性;配置中心的服務也會搭建兩套:研發與生產各自獨立部署。
二、配置方式
在專案中涉及到的配置非常多,型別也很多,但是從大的結構上可以分為:工程級、應用級、元件級三大塊;實際上這裡只是單純的從程式碼工程來看配置,如果把持續整合、自動化相關模組也考慮進來的話,配置的管理會更加複雜;
站在開發的角度來看,主要還是對應用級的配置進行管理,而在微服務的架構下,多個不同的服務既有大量相同的配置,又存在各種差異化的自定義引數,所以在維護上有一定的複雜性;
在單服務的工程中,應用中只會存在一個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