1. Nacos入門
1.1. 前言
我的初衷是想搞一套適合自定義快速開發的框架,把一些必要的技術整合進來,第一想法是嘗試下SpringCloud Alibaba,畢竟是阿里開發,適應國人需求,下載源碼本打算研究下,看了nacos,就想到之前項目用的Eureka,而我也深知Eureka真的只是單純的注冊中心,需要配置屬性還要依賴git做配置中心,所以改用nacos或許也是不錯的主意,於是開始研究nacos
1.2. 部署配置
我這入門和普通的運行demo入門不一樣,直接看源碼說話,先把SpringCloud Alibaba的git代碼下下來
由於nacos是個單獨部署的服務,所以先去Nacos官網安裝好nacos,賬號密碼都是nacos,修改配置文件nacos地址

1.3. 控制台配置
- 可以看到上述配置文件,明顯一個dataId對應了一個配置文件,group做為分組參數,我們可以再nacos界面進行配置


點擊發布,確認后就可以更新到應用,那么現在來研究它的原理
1.4. 調試分析
1.4.1. 獲取配置
隨意發布更新一個配置看到打印參數

那么進ClientWorker類,可以看到一個單獨的線程池負責了數據的接收

其中核心的更新配置方法為checkUpdateDataIds,而該方法內部的核心方法為checkUpdateConfigStr,其核心內容如下

明顯可以看到一個遠程post調用的方法,此方法用來獲取nacos的最新配置,到這里可能要問了,明明是nacos修改配置推送給客戶端,為什么是客戶端調用服務端?這里nacos用了個巧妙的方法,設置較長的超時時間,看截圖可以看出,超時時間為45秒,實際上nacos在30秒的時候若沒有修改數據,就會返回空數據表示沒有更新,若30秒以內有更新數據,則可以在這個45秒的請求中直接返回,看到這個設計我還真有點佩服作者腦洞的,這樣做相當於把心跳和數據傳輸結合起來了,不需要再額外搞個心跳連接,一旦nacos掛了,客戶端就會報錯,客戶端在請求則nacos也知道客戶端存活。我能想到的劣勢,或許就是這樣的長連接不能搞太多吧,但一般服務連接也夠了
接下來進入方法parseUpdateDataIdResponse,該方法用來確認修改了哪份配置,真正修改系統配置的還要回去繼續看

可以看到changedGroupKeys為改變的文件,nacos在拿到改變文件名稱后,會通過getServerConfig方法主動去獲取改變數據

之后通過LocalConfigInfoProcessor.saveSnapshot在本地存一個快照,更新本地緩存cacheMap
1.4.2. 刷新配置
-
遍歷緩存配置文件名

-
檢測數據變化

- 刷新屬性值

- 刷新容器,加載新的配置文件

-
加載對應環境配置文件

-
資源定位器

-
讀取位置,這里讀取的是故障轉移文件,也就是可以放自己的配置文件在nacos故障的時候可以使用

- 一般沒有故障轉移文件,則遠程調用獲取正確配置

- 構建PropertySource

1.5. 總結
整個流程下來可以總結為幾步
- 保持遠程連接,拉取改變的文件名
- 根據拉取的文件名,dataId等參數獲取真正的配置數據
- 更新緩存中配置,這里我發現好多地方都會經過
configFilterChainManager.doFilter(null, cr),我通過反射添加了過濾器,結果一次文件更新會調用好多遍doFilter,目前沒搞清它的意義是什么 - 發布配置更新事件
RefreshEvent,會重新加載propertySource,過程中會遠程調用Nacos再次去調用最新配置
1.6. 問題
沒想明白這冗余的設計,隨着源碼的深入,真的反轉反轉再反轉,劇情跌宕起伏
- 我以為在阻塞請求中會直接返回改變的配置參數,結果返回的是nacos中配置的文件名,好吧,這也可以理解,文件名用來定位具體的
propertySource也行,但為什么不和改變參數一起返回; - 那好,根據第一次返回的參數再去遠程調用獲取配置,我以為可能只返回修改的參數,返回的卻是整個文件參數,好吧,這樣容錯更好也能理解,就是一次可能傳遞的數據量會比較多,似乎和apollo的設計不同
- 我以為拿到數據應該會去更新環境變量,刷新實例了,但是也沒有,對着緩存操作了一通,然后直接刷新Spring容器,通過它早就定義好的資源定位器,重新遠程讀取配置內容,刷新到
propertySource中 - 好吧,中間的一系列操作,我姑且認為為其它依賴做准備,但多次出現的
configFilterChainManager.doFilter(null, cr)又看不懂了,看名字應該是鏈式過濾器,但哪有過濾器再一次更新中調用3次以上的,而且在沒刷新容器之前,也起不到過濾作用吧; - 並且這個過濾器定義了add方法,但我沒找到入口可以添加,只能通過反射強制讀出屬性進行過濾器的添加
- 我也看到了
listener.receiveConfigInfo(contentTmp);這么個方法,這個更像是對用戶提供的配置內容讀取方法,若想要拿到配置變動后新的配置信息,可別用filter去了,實現的例子如下

