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去了,實現的例子如下