SpringCloud Alibaba Nacos 配置動態更新原始碼學習總結

木马不是马發表於2024-07-03

眾所周知,nacos兩大核心功能,服務註冊發現與動態配置
支援服務註冊發現的有:Eureka、Consul、Zookeeper、Nacos
支援動態配置的有:Spring Cloud Config、Nacos、Apollo、Consul
像支援分散式的框架,必須得借用第三方服務,比如定時任務排程xxl-job,分散式事務seata,都分為server端與client端,client端即眾多微服務,server端需要單獨部署,好在nacos server端也是java編寫,所以在spring專案運用更廣泛
這裡nacos有springboot的版本,隸屬於nacos-group,有SpringCloud的版本,隸屬於alibaba,一般也會接入Cloud版本的

SpringCloud Alibab有很多出名的元件,這裡先記錄下服務配置的一些東西,服務註冊的後續有時間再看
首先,還是這兩個檔案,一個是新版本的自動配置,另一個是其他的配置

相比於AutoConfiguration、BootstrapConfiguration更最佳化,因為它會引導專案啟動,做專案啟動前的一些準備
開啟NacosConfigBootstrapConfiguration,首先映入眼簾的是NacosConfigProperties,spring.cloud.nacos.config為字首的配置檔案

接下來就是NacosConfigManager,主要作用是createConfigService方法建立ConfigService

可以看到的是一個很古老的單例模式

nacos的配置檔案載入完了,下面就是這個NacosPropertySourceLocator,算是比較重要的了,用於定位和獲取遠端配置,檢視其實現,發現Spring Cloud Config也用的這個,主要用於實現locate方法
在locate方法中可以看到Environment當前已經當前已經載入的propertySources,是一個CopyOnWriteList


下一個載入的就是當前nacos的Property,因為nacos配置儲存與服務端,所以在這個階段肯定會發起請求,得到服務端的詳細配置資訊
NacosPropertySourceBuilder 這個類裡面build方法會執行實際的邏輯,CompositePropertySource而這個是spring core的類,用於將nacos的property組裝到現有的property

loadApplicationConfiguration開始呼叫配置,會分為三次依次執行,例如專案名叫demo,檔案字尾叫yaml,根據檔案字尾第一次獲取demo, 第二次根據檔名字尾獲取demo.yaml,第三次根據activeProfile獲取,如果有多個則會獲取多次,比如dev環境,獲取demo-dev.yaml

並且根據順序優先順序越高,後面載入的優先順序會高於前面載入的,因為CompositePropertySource組裝所有的property的時候addFirstPropertySource是一個LinedHashSet,先刪除再插,將當前的排在第一位

NacosPropertySourceBuilder的loadNacosData根據上面的dataId以及group呼叫configService,configService是locate方法中透過nacosConfigManager透過單模式建立出來的,nacosConfigManager又透過NacosFactory工廠模式建立出來,
ConfigFactory使用代理模式最終建立

可以看到建立的是NacosConfigService。

在構造器中執行了不少邏輯,第一步建立http請求,同時在start裡面開啟了一個定時任務在專案啟動成功之前校驗serverUrl,第二部建立ClientWorker,主要建立執行緒池以及建立重新整理配置的定時任務,

同時獲取serverConfig真正發起請求的方法放在了ClientWorker裡面,tenant是上游傳過來的namespace,開啟呼叫/v1/cs/configs介面,值得注意的是這裡如果請求失敗直接會丟擲NacosException,而在後面沒有捕獲這個異常,所以如果nacos的serverAddress獲取哪個地址配置錯誤,專案直接會起不來的,

在NacosPropertySourceBuilder的loadNacosData方法中,獲取到了配置的資訊,下一步是透過NacosDataParserHandler轉換為Linedhashmap,因為MapPropertySource需要的source就是map結構

nacos控制端有text,json,yaml,html,properties,所以在解析的時候可以看到也有對應的幾種解析方式,但是剛才http返回的是一個string型別的,如何判斷服務端是哪一種型別的,


沒法判斷型別的話那就一個一個來吧,但是通常我們springboot會使用yaml格式,nacos正常也會使用yaml格式的,所以使用的責任鏈模式,依次執行,這裡可以看到首次執行的是yaml解析器


實際執行解析的邏輯還是借用spring裡面的YamlMapFactoryBean,解析完成就是下面這樣子的,下一步需要做的就是將多層的map進行攤平為一層

至此,遠端配置檔案以及請求到本地,並且解析完了,已經轉換為NacosPropertySource了,正常該進行組裝了,這裡為了後續邏輯,放入了NacosPropertySourceRepository快取

這裡nacos的三個property將會透過CompositePropertySource放入Environment中,

具體的組裝邏輯在PropertySourceBootstrapConfiguration裡面insertPropertySources
因為Environment的propertySource是CopyOnWriteList,並且放在最前面

到這裡配置就載入完成了,開始執行spring 載入邏輯了
再次檢視Enviornment發現最前面三個是剛才新建的nacos配置

動態更新配置

緊著這後面後掃描org.springframework.boot.autoconfigure.EnableAutoConfiguration
會發現裡面有個NacosContextRefresher


仔細一看,這個類大有來頭,實現了ApplicationListener,泛型是ApplicationReadyEvent,
spring實現釋出訂閱模式的事件監聽器主要有兩種方式,第一是實現ApplicationListener介面並且重寫onApplicationEvent,第二種是註解形式@EventListener
而監聽的這個事件ApplicationReadyEvent也是spring生命週期很重要的一個擴充套件點,標誌著完成了大多數的準備工作,準備開始處理業務請求,
這個時候Spring容器初始化完畢,所有的bean已經被例項化、配置和初始化完成;AutoCOnfiguration自動配置也已經完成;所有視線CommandLineRunner和ApplicationRunner介面的run方法也已經被執行;透過@PostConstruct以及實現了InitializingBean的afterPropertiesSet方法也已經執行完成。
並且在容器初始化之前已經請求過一次配置,這個時候只需要想辦法重新整理本地的配置與服務端一直就可以,正好借用ApplicationReadyEvent事件,所有準備工作做完之後開始初始化nacos的監聽器,來監聽服務端的更新

可以看到registerNacosListenersForApplications方法為當前應用建立了一個nacos的監聽器,而NacosPropertySourceRepository這個也是比較熟悉了,在放入CompositePropertySource之前先放入了NacosPropertySourceRepository裡面的快取當中取出NacosPropertySource,最終還是在ClientWorker中註冊監聽器

值得注意的是addCacheDataIfAbsent方法,CacheData是存放配置資訊內容的地方,CacheData會首先獲取本地快照檔案的內容,這個快照內容是傳送http請求完成之後立馬將原文放入快照檔案當中

一般本地快照檔案裡面是有內容的,預設也是開啟了遠端同步的,所以遠端同步完會覆蓋掉之前的content,並且md5值是根據content重新計算

nacos中的listeners也是一個CopyOnWriteArrayList,所以可以給每個CacheData新增多個監聽器,並且使用ManagerListenerWrap,可以看到這是正兒八經的裝飾器模式,lastCallMd5表示最後一次配置檔案的md5值

到這裡監聽器建立完了,並且set到CacheData裡面了,並且ClientWorker裡面的cachaeMap裡面就是當前的三個CacheData

所有監聽器都建立完了,那麼在哪開始執行的呢?
在建立ClientWorker的時候,開啟了一個10ms的定時任務,主要方法為checkConfigInfo,一直檢測cacheMap的情況,因為上面看了原始碼, 一旦cacheMap有值表示已經請求過服務端的配置資訊,並且監聽器已建立完成

為了防止有的專案配置檔案太多,這裡3000個cacheDate分為一個任務,並且開始執行長輪詢任務,也就是nacos動態配置核心的開始

上面的check failover config暫時不用看,只需要看check server config,一直到checkUpdateConfigStr,真正發起長輪詢的請求,請求服務端的/v1/cs/configs/listener介面

其中param為分組的任務,根據特殊符號進行分割,將dataId,group,namespace以及md5值傳到服務端,如果有3000個,將會拼接3000次,這裡的timeout為30000


緊接著檢視nacos服務端的程式碼,

首先會在controller中將拼接的客戶端資訊進行解析,然後會立馬進行一次執行,隨後如果有變化的話會 馬上返回,但是當沒有變化,會開啟非同步執行緒,非同步執行緒當中執行了一個延遲任務,而延遲時間預設為客戶端傳過來的30000-500,也就是29.5s

這裡ClientMd5Map是上面用特殊符號組裝的客戶端goup,以及當前的md5值這些,而ConfigCacheService.isUptodate會取出當前服務端最新配置的md5值,如果比較是不一致的,表示當前服務端的配置傳送了更新,因為有多個配置,所以在返回response的時候又利用特殊符號拼接進行返回,而此時,這個延遲任務也會結束,傳送響應

而客戶端接受到之後發現結果不為空,也就是最新的配置更新了,會根據剛才返回的grupKey進行請求配置資訊


緊接著會呼叫cacheData的checkListenerMd5,如果md5值不想同,會傳送監聽事件修改本地的配置

最終釋出RefreshEvent事件

在ContextRefresh中會重新進行重新整理Environment

相關文章