官方資源
https://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 對象提前寫入了。
極限就是為了超越而存在的