1. 全文共 2582 字,預計閱讀時間 12 分鐘
2. 本文不會貼太多源碼,基本靠圖片和文字敘述
3. 這是公衆號的第 53 篇文章
什麼是 Nacos
配置中心的架構
Nacos 使用示例
官方代碼示例
Properties 解讀
配置項的層級設計
Nacos 客戶端解析
獲取配置
註冊監聽器
配置長輪詢
Nacos 服務端解析
配置 Dump
配置註冊
處理長輪詢
全文總結
什麼是 Nacos
Nacos
是阿里發起的開源項目,Nacos
主要提供兩種服務,一是配置中心,支持配置註冊、變更下發、層級管理等,意義是不停機就可以動態刷新服務內部的配置項;二是作爲命名服務,提供服務的註冊和發現功能,通常用於在 RPC
框架的 Client
和 Server
中間充當媒介,還附帶有健康監測、負載均衡等功能。
本文聚焦於 Nacos
的第一塊功能,即配置中心的實現。先敘述一個配置中心通常需要哪些組成部分,再結合 Nacos 1.1.4
的源碼,探究一下這些設計是如何反映在源碼上的。
配置中心的架構
配置中心本身並不複雜,前提是你先將 CAP
的取捨問題晾在一邊的話。配置中心最基礎的功能就是存儲一個鍵值對,用戶發佈一個配置(configKey
),然後客戶端獲取這個配置項(configValue
);進階的功能就是當某個配置項發生變更時,將變更告知客戶端刷新舊值。
下方的架構圖,簡要描述了一個配置中心的大致架構,用戶可以通過管理平臺發佈配置,通過 HTTP
調用將配置註冊到服務端,服務端將之保存在 MySQL
等持久化存儲引擎中;用戶通過客戶端 SDK
訪問服務端的配置,同時建立 HTTP
的長輪詢監聽配置項變更,同時爲了減輕服務端壓力和保證容災特性,配置項拉取到客戶端之後會保存一份快照在本地文件中,SDK 優先讀取文件裏的內容。
這裏省略了許多細節問題,例如配置分層設計,權限校驗,客戶端長輪詢的間隔設置,服務端每次查詢都需要訪問 MySQL
麼,配置變更是主動推送還是等定時輪詢觸發等,還有就是運維高可用方面的工作(私以爲這個是配置中心的精華),例如節點跨地域部署,網絡分區時配置如何保證可寫可推送變更等。真正實現一個高質量的配置中心,還是需要長時間打磨的。
Nacos 使用示例
下文涉及的源碼均基於 Nacos 1.1.4 版本
官方代碼示例
先看一下官方文檔中對於 Nacos
的 API
使用的示例代碼,第一步是傳遞配置,新建 ConfigService
實例,第二步可以通過相應的接口獲取配置和註冊配置監聽器。使用方式非常簡單易懂,不再贅述。
Properties 解讀
serverAddr
傳遞的是配置中心服務端的地址列表,被內部名爲 ServerListManager
的類解析成地址列表進行管理,進行 HTTP
調用時會從中選擇存活的機器拼接成 URL
完成調用,一旦在調用時該地址拋異常,則客戶端會有一些處理措施,例如轉換下次選擇的節點等。值得注意的是,通常在實踐中不會採取這種硬編碼的方式,可以將其配置在 Zookeeper
或者註冊發現中心上,在啓動時動態拉取。
配置項的層級設計
Nacos
官方給出了這樣的設計圖:
dataId
可以理解爲用戶自定義的配置鍵,group
可以理解爲配置分組名稱,這個屬於配置層級設計的概念。簡單來說,配置中心會通過層次設計,來支持不同的分區,以此區分不同的環境、不同的分組、甚至不同的開發者,滿足在開發過程中灰度發佈、測試等需求。因此怎樣設計都可以,只要有含義就好,例如下圖也不是不可以。
Nacos 客戶端解析
獲取配置
獲取配置的主要方法是 NacosConfigService
類的 getConfigInner
方法,通常情況下該方法直接從本地文件中取得配置的值,如果本地文件不存在或者內容爲空,則再通過 HTTP GET
方法從遠端拉取配置,並保存到本地快照中。
當通過 HTTP
獲取遠端配置時,Nacos
提供了兩種熔斷策略,一是超時時間,二是最大重試次數,默認重試三次。
註冊監聽器
配置中心客戶端對某個配置項註冊監聽器是很常見的需求,達到在配置項變更的時候執行回調的功能。
iconfig.addListener(dataId, group, ml);
iconfig.getConfigAndSignListener(dataId, group, 1000, ml);
Nacos
可以通過以上方式註冊監聽器,它們內部的實現均是調用 ClientWorker
類的 addCacheDataIfAbsent
。其中 CacheData
是一個維護配置項和其下注冊的所有監聽器的實例,私以爲這個名字取得並不好,不容易理解。
所有的 CacheData
都保存在 ClientWorker
類中的原子 cacheMap
中,其內部的核心成員有:
其中,content
是配置內容,MD5
值是用來檢測配置是否發生變更的關鍵,內部還維護着一個若干監聽器組成的數組,一旦發生變更則依次回調這些監聽器。
配置長輪詢
ClientWorker
通過其下的兩個線程池完成配置長輪詢的工作,一個是單線程的 executor
,每隔 10ms
按照每 3000
個配置項爲一批次撈取待輪詢的 cacheData
實例,將其包裝成爲一個 LongPollingTask
提交進入第二個線程池 executorService
處理。
該長輪詢任務內部主要分爲四步:
檢查本地配置,忽略本地快照不存在的配置項,檢查是否存在需要回調監聽器的配置項
如果本地沒有配置項的,從服務端拿,返回配置內容發生變更的鍵值列表
每個鍵值再到服務端獲取最新配置,更新本地快照,補全之前缺失的配置
檢查
MD5
標籤是否一致,不一致需要回調監聽器
如果該輪詢任務拋出異常,等待一段時間再開始下一次調用,減輕服務端壓力。另外,Nacos
在 HTTP
工具類中也有限流器的代碼,通過多種手段降低輪詢或者大流量情況下的風險。下文還會講到,如果在服務端沒有發現變更的鍵值,那麼服務端會夯住這個 HTTP
請求一段時間(客戶端側默認傳遞的超時是 30s
),以此進一步減輕客戶端的輪詢頻率和服務端的壓力。
Nacos 服務端解析
配置 Dump
服務端啓動時就會依賴 DumpService
的 init
方法,從數據庫中 load
配置存儲在本地磁盤上,並將一些重要的元信息例如 MD5
值緩存在內存中。服務端會根據心跳文件中保存的最後一次心跳時間,來判斷到底是從數據庫 dump
全量配置數據還是部分增量配置數據(如果機器上次心跳間隔是 6h
以內的話)。
全量 dump
當然先清空磁盤緩存,然後根據主鍵 ID
每次撈取一千條配置刷進磁盤和內存。增量 dump
就是撈取最近六小時的新增配置(包括更新的和刪除的),先按照這批數據刷新一遍內存和文件,再根據內存裏所有的數據全量去比對一遍數據庫,如果有改變的再同步一次,相比於全量 dump
的話會減少一定的數據庫 IO
和磁盤 IO
次數。
配置註冊
Nacos
服務端是一個 SpringBoot
實現的服務,註冊配置主要代碼位於 ConfigController
和 ConfigServletInner
中。服務端一般是多節點部署的集羣,因此請求一開始只會打到一臺機器,這臺機器將配置插入 MySQL
中進行持久化,這部分代碼很簡單不再贅述。
因爲服務端並不是針對每次配置查詢都去訪問 MySQL
的,而是會依賴 dump
功能在本地文件中將配置緩存起來。因此當單臺機器保存完畢配置之後,需要通知其他機器刷新內存和本地磁盤中的文件內容,因此它會發佈一個名爲 ConfigDataChangeEvent
的事件,這個事件會通過 HTTP
調用通知所有集羣節點(包括自身),觸發本地文件和內存的刷新。
處理長輪詢
上文提到,客戶端會有一個長輪詢任務,拉取服務端的配置變更,那麼服務端是如何處理這個長輪詢任務的呢?源碼邏輯位於 LongPollingService
類,其中有一個 Runnable
任務名爲 ClientLongPolling
,服務端會將受到的輪詢請求包裝成一個 ClientLongPolling
任務,該任務持有一個 AsyncContext
響應對象(Servlet 3.0
的新機制),通過定時線程池延後 29.5s
執行。
爲什麼比客戶端
30s
的超時時間提前500ms
返回是爲了最大程度上保證客戶端不會因爲網絡延時造成超時
這裏需要注意的是,在 ClientLongPolling
任務被提交進入線程池待執行的同時,服務端也通過一個隊列 allSubs
保存了所有正在被夯住的輪詢請求,這是因爲在配置項被夯住的期間內,如果用戶通過管理平臺操作了配置項變更、或者服務端該節點收到了來自其他節點的 dump
刷新通知,那麼都應立即取消夯住的任務,及時通知客戶端數據發生了變更。
爲了達到這個目的,LongPollingService
類繼承自 Event
接口,實際上本身是個事件觸發器,需要實現 onEvent
方法,其事件類型是 LocalDataChangeEvent
。
當服務端在請求被夯住的期間接收到某項配置變更時,就會發佈一個 LocalDataChangeEvent
類型的事件通知(注意同上文中的 ConfigDataChangeEvent
區別),之後會將這個變更包裝成一個 DataChangeTask
異步執行,內容就是從 allSubs
中找出夯住的 ClientLongPolling
請求,寫入變更強制其立即返回。
因此完整的流程如下,如果非接收請求的節點,那麼忽略第一步持久化配置後開始:
全文總結
本文聚焦於 Nacos
作爲配置中心的源碼實現,包含了客戶端和服務端兩部分,內容基本覆蓋了配置中心功能的關鍵點,既作爲學習總結,也希望對閱讀的朋友有所幫助。