在 Nacos console 修改了配置以後,服務端底層怎麼存儲配置?客户端怎麼知道配置修改了?怎麼通知集羣其他節點?讓我來揭開它神秘的面紗。
服務端接收配置更新請求
在控制枱頁面更新一項配置,看看控制枱發送了什麼請求給服務端。
控制枱發送了一個 POST 請求:/nacos/v1/cs/configs,在官方 API指南 可以找到 API 定義。
我 Nacos 源碼是 2.* 版本,但是打開的 console 發佈配置請求的 API 還是 1.* 版本。
官方説v2 是兼容 v1 的,在源碼的 Controller 層面兩個版本使用的也是相同的 Service 類處理請求。
請求進入com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,封裝配置和請求信息後調用ConfigOperationService#publishConfig。
publishConfig方法做了兩件事:
- 添加或更新配置信息到數據庫
- 發佈配置數據變更事件
ConfigDataChangeEvent,這是客户端能感知配置更新的根本原因
ConfigDataChangeEvent被兩個類監聽:
DumpService: 將配置信息轉存到本地磁盤AsyncNotifyService:通知集羣其他節點和訂閲配置的客户端配置發生變更
DumpService:轉存配置信息到本地
DumpService在構造函數中訂閲了ConfigDataChangeEvent事件,當監聽到事件發生,調用handleConfigDataChange方法。
我回看了一下前面的文章,一些不重要的方法也有截圖,白白的浪費了大家的閲讀時間,對於簡單的方法後面只會列出方法調用鏈路
- 👇
handleConfigDataChange(Event event) - 👇
dumpFormal(String dataId, String group, String tenant, long lastModified, String handleIp)
dumpFormal方法作用是轉存正式數據到本地磁盤和緩存,向任務管理器dumpTaskMgr提交了一個 任務DumpTask。
dumpTaskMgr是TaskManager實例,在DumpService的構造函數中創建了該實例,並且為它指定了DumpProcessor作為默認任務處理器
TaskManager類本身沒有實現執行任務的方法,而是繼承自 NacosDelayTaskExecuteEngine, 來看下他是如何執行任務的。
在構造函數中創建單線程執行器,確保任務都能處理成功,並且每個任務之間間隔 100 毫秒。
執行器會一直執行ProcessRunnable,ProcessRunnable實現了Runnable接口,在run方法內調用processTaks方法處理任務
通過 taskKey 獲取NacosTaskProcessor類型的處理器,調用處理器的process方法處理任務。這裏實現了任務重試機制,如果執行失敗則放入隊列稍後執行。
NacosTaskProcessor是一個處理器接口,他有很多實現,分別用來處理不同的任務
dump 正式數據的任務則是由上面指定的默認任務處理器DumpProcessor來處理。
調用處理器的的process方法處理任務:
- 👇process(NacosTask task)
- 👇DumpConfigHandler.configDump(build.build())
- 👇ConfigCacheService.dump(dataId, group, namespaceId, content, lastModified, event.getType(), event.getEncryptedDataKey())
- 👇dumpWithMd5(dataId, group, tenant, content, null, lastModifiedTs, type, encryptedDataKey)
- 👇ConfigDiskServiceFactory.getInstance().saveToDisk(dataId, group, tenant, content)
調用saveToDisk方法保存到磁盤;updateMd5方法更新本地緩存的 md5和最後更新時間,併發布LocalDataChangeEvent事件。
進入com.alibaba.nacos.config.server.service.dump.disk.ConfigRawDiskService#saveToDisk, 可以看到targetFile就是nacos.home下要更新的文件路徑,向文件寫入新的配置信息。
繼續看updateMd5方法,更新本地JVM緩存中的配置以後,發佈LocalDataChangeEvent本地數據變更事件,這個事件的目的就是告訴客户端:配置發生了變更
AsyncNotifyService:通知集羣節點
上面DumpService把修改後的配置更新到數據庫,磁盤,JVM緩存了,但是這都是在本機發生的,集羣的其他節點還是舊的配置呢,那麼如何同步呢?
AsyncNotifyService異步通知服務就發揮作用了,它也監聽了ConfigDataChangeEvent事件,所以當配置變更時,它就負責通知其它節點。
當事件發生時,調用handleConfigDataChangeEvent()
- 獲取除了自己以外的集羣成員
- 為每個成員創建一個
NotifySingleRpcTask放入同一隊列中 - 創建一個
AsyncRpcTask去處理隊列中的任務
AsyncRpcTask調用executeAsyncRpcTask()處理任務
- 依次從隊列取出任務
- 構建集羣同步請求體
ConfigChangeClusterSyncRequest,集羣其他節點收到此類型請求,會進行數據同步 - 檢查集羣成員節點健康狀態
- 調用
configClusterRpcClientProxy.syncConfigChange()通知配置變更
集羣成員收到請求會做什麼呢
ConfigChangeClusterSyncRequestHandler繼承了RequestHandler,處理ConfigChangeClusterSyncRequest類型的請求。
- 從請求體中拿到配置修改的關鍵信息,比如dataId,group
- 調用
DumpService.dump() dump()調用dumpFormal()轉存配置信息到本地
通知客户端配置變更
上面在DumpService小節中講到,更新了本地JVM緩存以後,發佈了LocalDataChangeEvent本地數據變更事件。
一共有兩處監聽了LocalDataChangeEvent本地數據變更事件
- RpcConfigChangeNotifier
- LongPollingService
RpcConfigChangeNotifier
RpcConfigChangeNotifier訂閲了LocalDataChangeEvent事件,事件發生時調用configDataChanged方法通知配置的監聽者。
- 首先獲取監聽配置的客户端列表、
- 創建通知請求
ConfigChangeNotifyRequest,客户端收到這個請求後,會重新獲取配置 - 為每個客户端創建
RpcPushTask
然後將任務放入執行器準備執行。
- 👇ConfigExecutor.scheduleClientConfigNotifier(retryTask, retryTask.getTryTimes() * 2, TimeUnit.SECONDS)
Rpc推送任務
RpcPushTask實現了Runnable接口,它的run方法做了兩件事:
- 檢查
TPS,避免服務器資源耗盡。官方稱為反脆弱,可參考反脆弱插件 - 推送請求並設置回調
調用RpcPushService.pushWithCallback()發送GRPC請求通知客户端配置變更
在客户端的ClientWorker中註冊了ConfigChangeNotifyRequest的處理器,當收到請求後調用notifyListenConfig()更新配置。
在《nacos源碼分析-客户端啓動與配置動態更新的實現細節》中講過ClientWorker 主要用於封裝與 Nacos 配置服務的交互邏輯,提供配置的獲取、監聽和更新等功能。
LongPollingService
Nacos1.*版本 是基於長輪詢機制監聽配置變更,客户端調用/nacos/v1/cs/configs/listenerapi監聽配置(參考API指南)。
當配置發生變更時,也需要通知那些通過長輪詢監聽的客户端。
從下圖可以看到本地數據變更事件LocalDataChangeEvent發生時,創建了一個DataChangeTask放入執行器執行。
DataChangeTask遍歷全部訂閲者,如果客户端監聽了修改的配置 key,那麼移除和客户端的訂閲關係,因為客户端即將接收響應,然後向客户端發送響應
調用sendResponse()響應客户端請求
調用generateResponse()返回修改的配置 key 給客户端
原理圖
ProcessOn 地址:www.processon.com/diagraming/…
總結
現在可以來解答開頭的問題了。
1.服務端底層怎麼存儲配置?
- 添加或更新配置信息到數據庫
- 發佈配置數據變更事件
ConfigDataChangeEvent DumpService監聽到ConfigDataChangeEvent事件,將配置保存到本地磁盤和JVM緩存
2.客户端怎麼知道配置修改了?
- 本地緩存更新後,發佈
LocalDataChangeEvent事件 RpcConfigChangeNotifier監聽到本地數據變更事件LocalDataChangeEvent- 創建通知請求
ConfigChangeNotifyRequest,發起 GRPC調用通知客户端 - 通過長輪詢監聽的客户端,則由
LongPollingService攜帶更新的配置 key 響應客户端
3.怎麼通知集羣其他節點?
AsyncNotifyService監聽配置數據變更事件ConfigDataChangeEvent- 監聽到事件發生後,向集羣成員發送
ConfigChangeClusterSyncRequest,告知變更的配置信息