🏆【Alibaba中間件技術系列】「Nacos技術專題」配置中心加載原理和配置實時更新原理分析(中)
- 2022 年 1 月 21 日
- 筆記
- 【技術專區-Alibaba】
官方資源
//nacos.io/zh-cn/docs/quick-start.html
帶着問題去思考
- 客戶端長輪詢的響應時間會受什麼影響
- 為什麼更改了配置信息後客戶端會立即得到響應
- 客戶端的超時時間為什麼要設置為30s
- 帶着以上這些問題我們從服務端的代碼中去探尋結論。
Nacos之配置中心
- 動態配置管理是 Nacos的三大功能之一,通過動態配置服務,可以在所有環境中以集中和動態的方式管理所有應用程序或服務的配置信息。
- 動態配置中心可以實現配置更新時無需重新部署應用程序和服務即可使相應的配置信息生效,這極大了增加了系統的運維能力。
動態配置
Nacos的動態配置的能力,看看 Nacos是如何以簡單、優雅、高效的方式管理配置,實現配置的動態變更的,接下來來了解下 Nacos 的動態配置的功能。
客戶端動態化配置機制
Nacos 的客戶端維護了一個長輪詢的任務,去檢查服務端的配置信息是否發生變更,如果發生了變更,那麼客戶端會拿到變更的 groupKey 再根據 groupKey 去獲取配置項的最新值即可。
客戶端去發請求,詢問服務端我所關注的配置項有沒有發生變更,如果間隔時間設置的太長的話有可能無法及時獲取服務端的變更,如果間隔時間設置的太短的話,那麼頻繁的請求對於服務端來說無疑也是一種負擔。
如果客戶端每隔一段長度適中的時間去服務端請求,而在這期間如果配置發生變更,服務端能夠主動將變更後的結果推送給客戶端,這樣既能保證客戶端能夠實時感知到配置的變化,也降低了服務端的壓力。
客戶端長輪詢
客戶端長輪詢的部分,也就是LongPollingRunnable中的checkUpdateDataIds 方法,該方法就是用來訪問服務端的配置是否發生變更的,該方法最終會調用如下圖所示的方法:
http請求操作
客戶端是通過一個http的post 請求去獲取服務端的結果的,並且設置了一個超時時間:30s。一般來講:客戶端足足等了29.5+s,才請求到服務端的結果,然後客戶端得到服務端的結果之後,再做一些後續的操作,全部都執行完畢之後,在 finally 中又重新調用了自身,也就是說這個過程是一直循環下去的。
長輪詢執行邏輯
客戶端向服務端發起一次請求,最少要29.5s才能得到結果,當然啦,這是在配置沒有發生變化的情況下。如果客戶端在長輪詢時配置發生變更的話,該請求需要多長時間才會返回呢,在客戶端長輪詢時修改配置。
未獲得到修改數據的操作觸發返回
獲得到了修改數據操作立刻觸發返回
服務端controller
上面說到了客戶端發送的 http 請求中可以知道,請求的是服務端的 /v1/cs/configs/listener 這個接口,com.alibaba.nacos.config.server.controller.ConfigController.java,在 ConfigController 類中,如下圖所示:
服務端是通過springMVC對外提供的 http 服務,對 HttpServletRequest 中的參數進行轉換後,然後交給一個叫 inner 的對象去執行。inner 對象是 ConfigServletInner 類的實例,com.alibaba.nacos.config.server.controller.ConfigServletInner.java
該方法是一個輪詢的接口,除了支持長輪詢外還支持短輪詢的邏輯。再次進入 longPollingService 的 addLongPollingClient 方法,如下圖所示:
com.alibaba.nacos.config.server.service.LongPollingService.java
該方法主要是將客戶端的長輪詢請求添加到某個東西中去:服務端將客戶端的長輪詢請求封裝成一個叫 ClientLongPolling 的任務,交給 scheduler 去執行。
服務端拿到客戶端提交的超時時間後,又減去了 500ms 也就是說服務端在這裡使用了一個比客戶端提交的時間少 500ms 的超時時間,也就是 29.5s,看到這個 29.5s 我們應該有點興奮了。
PS:這裡的 timeout 不一定一直是 29.5,當 isFixedPolling() 方法為 true 時,timeout 將會是一個固定的間隔時間,這裡為了描述簡單就直接用 29.5 來進行說明。
接下來我們來看服務端封裝的 ClientLongPolling 的任務到底執行的什麼操作,如下圖所示:
com.alibaba.nacos.config.server.service.LongPollingService.ClientLongPolling.java
ClientLongPolling 被提交給 scheduler 執行之後,實際執行的內容可以拆分成以下四個步驟:
- 創建一個調度的任務,調度的延時時間為 29.5s。
- 將該 ClientLongPolling 自身的實例添加到一個 allSubs 中去。
- 延時時間到了之後,首先將該 ClientLongPolling 自身的實例從 allSubs 中移除。
- 獲取服務端中保存的對應客戶端請求的 groupKeys 是否發生變更,將結果寫入 response 返回給客戶端。
allSubs 對象,該對象是一個 ConcurrentLinkedQueue 隊列,ClientLongPolling 將自身添加到隊列中。
調度任務
服務端對客戶端提交上來的 groupKey 進行檢查,如果發現某一個 groupKey 的 md5 值還不是最新的,則說明客戶端的配置項還沒發生變更,所以將該 groupKey 放到一個 changedGroupKeys 列表中,最後將該 changedGroupKeys 返回給客戶端。對於客戶端來說,只要拿到 changedGroupKeys 即可。
服務端數據變更
服務端直到調度任務的延時時間到了之前,ClientLongPolling 都不會有其他的任務可做,所以在這段時間內,該 allSubs 隊列肯定有事情需要進行處理。
在客戶端長輪詢期間,更改了配置之後,客戶端能夠立即得到響應。
服務端數據變更接口
調用的請求,可以很容易的找到該請求對應的 url為:/v1/cs/configs 並且是一個 POST 請求,具體的方法是 ConfigController 中的 publishConfig 方法,如下圖所示:
修改配置後,服務端首先將配置的值進行了持久化層的更新,然後觸發了一個 ConfigDataChangeEvent 的事件,fireEvent 的方法:
com.alibaba.nacos.config.server.utils.event.EventDispatcher.java
fireEvent 方法實際上是觸發的 AbstractEventListener 的 onEvent 方法,而所有的 listener 是保存在一個叫 listeners 對象中的。
被觸發的 AbstractEventListener 對象則是通過 addEventListener 方法添加到 listeners 中的,找到 addEventListener 方法在何處被調用的,就知道有哪些 AbstractEventListener 需要被觸發 onEvent 回調方法了。
可以找到是在 AbstractEventListener 類的構造方法中,將自身註冊進去了,如下圖所示:
com.alibaba.nacos.config.server.utils.event.EventDispatcher.AbstractEventListener.java
可以看到 AbstractEventListener 所有的子類中LongPollingService。當我們從 dashboard 中更新了配置項之後,實際會調用到 LongPollingService 的 onEvent 方法。
回到 LongPollingService 中,查看一下 onEvent 方法,如下圖所示:
com.alibaba.nacos.config.server.service.LongPollingService.DataChangeTask.java
發現當觸發了 LongPollingService 的 onEvent 方法時,實際是執行了一個叫 DataChangeTask 的任務,應該是通過該任務來通知客戶端服務端的數據已經發生了變更,我們進入 DataChangeTask 中看下具體的代碼,如下圖所示:
遍歷 allSubs 的隊列
遍歷 allSubs 的隊列,該隊列中維持的是所有客戶端的請求任務,需要找到與當前發生變更的配置項的 groupKey 相等的 ClientLongPolling 任務
往客戶端寫響應數據
丟i與ClientLongPolling 任務後,只需要將發生變更的 groupKey 通過該 ClientLongPolling 寫入到響應對象中,就完成了一次數據變更的 「推送」 操作了
如果 DataChangeTask 任務完成了數據的 「推送」 之後,需要將原來等待執行的調度任務取消掉了,這樣就防止了推送操作寫完響應數據之後,調度任務又去寫響應數據。
可以從 sendResponse 方法中看到,確實是這樣做的:
http請求本來就是無狀態的,所以沒必要也不能將超時時間設置的太長,這樣是對資源的一種浪費。
與此同時服務端也將該請求封裝成一個調度任務去執行,等待調度的期間就是等待 DataChangeTask 主動觸發的,如果延遲時間到了 DataChangeTask 還未觸發的話,則調度任務開始執行數據變更的檢查,然後將檢查的結果寫入響應對象,如下圖所示:
總結結論:
- Nacos 客戶端會循環請求服務端變更的數據,並且超時時間設置為30s,當配置發生變化時,請求的響應會立即返回,否則會一直等到 29.5s+ 之後再返迴響應
- Nacos 客戶端能夠實時感知到服務端配置發生了變化。
- 實時感知是建立在客戶端拉和服務端「推」的基礎上,但是這裡的服務端「推」需要打上引號,因為服務端和客戶端直接本質上還是通過 http 進行數據通訊的,之所以有「推」的感覺,是因為服務端主動將變更後的數據通過 http 的 response 對象提前寫入了。