工程可以有點小亂,但設定不能含糊;
在微服務的程式碼工程中,設定管理是一項複雜的事情,即需要做好各個環境的設定隔離措施,還需要確保生產環境的設定安全;如果劃分的微服務足夠的多,還要考慮設定更新時的效率;
常規情況下,在設定管理的體系中,分為四個主要的環境:開發、測試、灰度、生產;通常來說除了運維團隊之外,其他人員沒有檢視灰度和生產設定的許可權,以此來保證設定的安全性;設定中心的服務也會搭建兩套:研發與生產各自獨立部署。
在專案中涉及到的設定非常多,型別也很多,但是從大的結構上可以分為:工程級、應用級、元件級三大塊;實際上這裡只是單純的從程式碼工程來看設定,如果把持續整合、自動化相關模組也考慮進來的話,設定的管理會更加複雜;
站在開發的角度來看,主要還是對應用級的設定進行管理,而在微服務的架構下,多個不同的服務既有大量相同的設定,又存在各種差異化的自定義引數,所以在維護上有一定的複雜性;
在單服務的工程中,應用中只會存在一個bootstrap.yml
組態檔,設定內容基本就是服務名稱,和設定中心地址等常規內容,其他複雜的設定都被封閉維護,避免核心內容洩露引發安全問題;
設定主體通常會被拆分成如下幾個層次:環境控制,用來識別灰度和生產;應用基礎,管理和載入各個服務的通用設定;服務差異則設定在各自獨立的檔案內;並且對多個設定進行分類分層管理;以此保證設定的安全和降低維護難度。
首先還是從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);
}
}
從原始碼的結構圖來看,組態檔的載入邏輯也是採用事件模型實現的,這在前面任務管理中有詳細的描述;設定的核心作用就是在程式啟動時進行載入引導,這裡關鍵要理解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