目錄
- 前言
- 1. 用戶端擷取 Nacos 伺服器裡的配置
- 1.1 定位 Nacos 配置源 NacosPropertySourceLocator.locate()
- 2. Nacos 配置的事件訂閱機制
- 2.1 監聽 ApplicationReadyEvent 事件,注冊監聽器 NacosContextRefresher.onApplicationEvent()
- 2.2 注冊 Nacos 監聽器,監聽配置變更 NacosContextRefresher.registerNacosListener()
- 2.3 監聽配置變更,實施變更 RefreshEventListener.handle()
- 3. 源碼結構圖小結
- 3.1 用戶端擷取 Nacos 伺服器上的配置源碼結構圖
- 3.2 Nacos 配置的事件訂閱機制
- 最後
參考資料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚矽谷 SpringCloud 架構開發教程 周陽》
為友善了解與表達,這裡把 Nacos 控制台和 Nacos 注冊中心稱為 Nacos 伺服器(就是 web 界面那個),我們編寫的業務服務稱為 Nacso 用戶端;
由于篇幅有限,這裡将源碼分析分為上下兩篇,其中上篇講擷取配置與事件訂閱機制,下篇講長輪詢定時機制;
上篇《微服務架構 | *2.3 Spring Cloud 啟動及加載配置檔案源碼分析(以 Nacos 為例)》中提到,讀取 Nacos 伺服器裡的配置依靠的是
NacosPropertySourceLocator.locate()
方法,我們這次的源碼之旅将從這個方法開始;
- 該方法的主要作用是:
- 初始化 ConfigService 對象,這是 Nacos 用戶端提供的用于通路實作配置中心基本操作的類;
- 按照順序分别加載共享配置、擴充配置、應用名稱對應的配置;
- 方法源碼如下:
@Override
public PropertySource<?> locate(Environment env) {
//【斷點步入 長輪詢定時機制】擷取配置伺服器執行個體,這是 Nacos 用戶端提供的用于通路實作配置中心基本操作的類
ConfigService configService = nacosConfigProperties.configServiceInstance();
if (null == configService) {
log.warn("no instance of config service found, can't load config from nacos");
return null;
}
long timeout = nacosConfigProperties.getTimeout();
//Nacos 屬性源生成器
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
String name = nacosConfigProperties.getName();
//DataId 字首(這裡是 nacos-config-client)
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
//沒有配置 DataId 字首則用 spring.application.name 屬性的值
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
//建立複合屬性源
CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
//加載共享配置
loadSharedConfiguration(composite);
//加載外部配置
loadExtConfiguration(composite);
//【斷點步入】加載 Nacos 伺服器上應用程式名對應的的配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
- 進入
方法,根據 Data ID 加載配置;NacosPropertySourceLocator.loadApplicationConfiguration()
private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {
//擷取配置格式(這裡是 yaml)
String fileExtension = properties.getFileExtension();
//擷取nacosGroup(這裡是 DEFAULT_GROUP)
String nacosGroup = properties.getGroup();
//如果我們配置了字首,則按字首擷取配置檔案(由于我們沒配置字首,這裡是 nacos-config-client.yaml 擷取不到)
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
for (String profile : environment.getActiveProfiles()) {
//這裡是 nacos-config-client-dev.yaml 可以擷取
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
//【斷點步入】加載配置
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);
}
}
-
方法,判斷是否更新情況;NacosPropertySourceLocator.loadNacosDataIfPresent()
private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) {
//擷取更新的配置,需要注意,NacosContextRefresher 類與事件訂閱機制相關,本篇第2點将重點讨論
if (NacosContextRefresher.getRefreshCount() != 0) {
NacosPropertySource ps;
if (!isRefreshable) {
ps = NacosPropertySourceRepository.getNacosPropertySource(dataId);
}
else {
ps = nacosPropertySourceBuilder.build(dataId, group, fileExtension, true);
}
composite.addFirstPropertySource(ps);
}
else {
//【斷點步入】如果我們沒有更新配置,則走下面代碼
NacosPropertySource ps = nacosPropertySourceBuilder.build(dataId, group,fileExtension, isRefreshable);
composite.addFirstPropertySource(ps);
}
}
-
方法,加載并封裝配置;NacosPropertySourceBuilder.build()
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
//【斷點步入】加載 Nacos
Properties p = loadNacosData(dataId, group, fileExtension);
//将擷取到的配置封裝到 NacosPropertySource
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
return nacosPropertySource;
}
-
方法,NacosPropertySourceBuilder.loadNacosData()
private Properties loadNacosData(String dataId, String group, String fileExtension) {
//省略其他代碼
try {
//【斷點步入】根據 dataId、group 等資訊擷取配置
data = configService.getConfig(dataId, group, timeout);
}
catch (NacosException e) {
log.error("get data from Nacos error,dataId:{}, ", dataId, e);
}
return EMPTY_PROPERTIES;
}
- 一直追下去發現在
方法裡成功擷取到配置;NacosConfigService.getConfigInner()
- 上下文準備完畢後,程式運作,EventPublishingRunListener 釋出 ApplicationReadyEvent 事件,詳情請見《微服務架構 | *2.3 Spring Cloud 啟動及加載配置檔案源碼分析(以 Nacos 為例)》中的《4. 程式運作事件》;
- 上面 1.1 提到,事件訂閱機制與 NacosContextRefresher(Nacos上下文更新器) 相關,這是因為裡面有個
方法實作了對事件 ApplicationReadyEvent(上下文準備完畢事件) 的監聽,源碼如下:NacosContextRefresher.onApplicationEvent()
//監聽 ApplicationReadyEvent 事件
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) {
//【斷點步入】為應用程式注冊 Nacos 監聽器
this.registerNacosListenersForApplications();
}
}
- 當監聽到 ApplicationReadyEvent 事件後,最終會調用
方法來實作 Nacos 監聽器的注冊,源碼如下:NacosContextRefresher.registerNacosListener()
private void registerNacosListener(final String group, final String dataId) {
Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
//接受配置變更的回調
@Override
public void receiveConfigInfo(String configInfo) {
refreshCountIncrement();
String md5 = "";
if (!StringUtils.isEmpty(configInfo)) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8"))).toString(16);
}
catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
}
}
refreshHistory.add(dataId, md5);
//釋出 RefreshEvent 配置變更事件
applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
try {
//監聽配置
configService.addListener(dataId, group, listener);
}
catch (NacosException e) {
e.printStackTrace();
}
}
- 當收到配置變更的回調時,會通過
釋出一個 RefreshEvent 事件;applicationContext.publishEvent()
- 該事件又會被 RefreshEventListener(事件更新監聽器) 監聽,源碼如下:
public void onApplicationEvent(ApplicationEvent event){
if (event instanceof ApplicationReadyEvent) {
//監聽 ApplicationReadyEvent 事件
this.handle((ApplicationReadyEvent)event);
} else if (event instanceof RefreshEvent) {
//【斷點步入 2.3】監聽 RefreshEvent 事件
this.handle((RefreshEvent)event);
}
}
- RefreshEventListener(事件更新監聽器) 類使用
方法變更配置,源碼如下:RefreshEventListener.handle()
public void handle(RefreshEvent event) {
if (this.ready.get()) {
log.debug("Event received " + event.getEventDesc());
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
- NacosPropertySourceLocator.locate():初始化 ConfigService 對象,定位配置;
- NacosPropertySourceLocator.loadApplicationConfiguration():根據 Data ID 加載配置;
- NacosPropertySourceLocator.loadNacosDataIfPresent():判斷是否更新配置;
- NacosPropertySourceBuilder.build():加載并封裝配置;
- NacosPropertySourceBuilder.loadNacosData():加載配置;
- NacosConfigService.getConfig():使用配置服務擷取配置;
- NacosConfigService.getConfigInner():最終在這裡擷取到配置;
- NacosConfigService.getConfig():使用配置服務擷取配置;
- NacosPropertySourceBuilder.loadNacosData():加載配置;
- NacosPropertySourceBuilder.build():加載并封裝配置;
- NacosPropertySourceLocator.loadNacosDataIfPresent():判斷是否更新配置;
- NacosPropertySourceLocator.loadApplicationConfiguration():根據 Data ID 加載配置;
- 上下文準備完畢,程式運作,EventPublishingRunListener 釋出
事件;ApplicationReadyEvent
- NacosContextRefresher.onApplicationEvent():監聽 ApplicationReadyEvent 事件;
- NacosContextRefresher.registerNacosListener():注冊 Nacos 監聽器,監聽配置變更;
- 變更發生時,NacosContextRefresher 釋出一個
RefreshEvent
- RefreshEventListener.onApplicationEvent():同時監聽 ApplicationReadyEvent 和 RefreshEvent 事件;
- RefreshEventListener.handle():實施變更方法;
新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公衆号,會分享一些更日常的東西!
如需轉載,請标注出處!