Nacos配置中心原理

逅弈逐碼 發表於 2022-12-08

動態配置管理是 Nacos 的三大功能之一,透過動態配置服務,我們可以在所有環境中以集中和動態的方式管理所有應用程式或服務的配置資訊。

動態配置中心可以實現配置更新時無需重新部署應用程式和服務即可使相應的配置資訊生效,這極大了增加了系統的運維能力。

動態配置

下面我將來和大家一起來了解下 Nacos 的動態配置的能力,看看 Nacos 是如何以簡單、優雅、高效的方式管理配置,實現配置的動態變更的。

我們用一個簡單的例子來了解下 Nacos 的動態配置的功能。

環境準備

首先我們要準備一個 Nacos 的服務端,現在有兩種方式獲取 Nacos 的服務端:

  • 1.透過原始碼編譯

  • 2.下載 Release 包

兩種方法可以獲得 Nacos 的可執行程式,下面我用第一種方式透過原始碼編譯一個可執行程式,可能有人會問為啥不直接下載 Release 包,還要自己去編譯呢?首先 Release 包也是透過原始碼編譯得到的,其次我們透過自己編譯可以瞭解一些過程也有可能會碰到一些問題,這些都是很重要的經驗,好了那我們直接原始碼編譯吧。

首先 fork 一份 nacos 的程式碼到自己的 github 庫,然後把程式碼 clone 到本地。

然後在專案的根目錄下執行以下命令(假設我們已經配置好了 java 和 maven 環境):

mvn -Prelease-nacos clean install -U

執行成功之後你將會看到如下圖所示的結果:

Nacos配置中心原理

然後在專案的 distribution 目錄下我們就可以找到可執行程式了,包括兩個壓縮包,這兩個壓縮包就是nacos 的 github 官網上釋出的 Release 包。

Nacos配置中心原理

接下來我們把編譯好的兩個壓縮包複製出來,然後解壓出來直接使用,這樣就相當於我們下載了 Release 包了。解壓後檔案結構和 nacos-server-0.8.0 一樣,我們直接執行 startup.sh 即可啟動一個單機的 Nacos 服務端了。

啟動服務端

執行下列命令來啟動一個 Nacos 服務端:

sh startup.sh -m standalone

啟動完你將會看到如下圖所示的結果:

Nacos配置中心原理

啟動成功後,我們就可以訪問 Nacos 的控制檯了,如下圖所示:

Nacos配置中心原理

控制檯做了簡單的許可權控制,預設的賬號和密碼都是 nacos。

登入進去之後,是這樣的:

Nacos配置中心原理

新建配置

接下來我們在控制檯上建立一個簡單的配置項,如下圖所示:

Nacos配置中心原理

啟動客戶端

當服務端以及配置項都準備好之後,就可以建立客戶端了,如下圖所示新建一個 Nacos 的 ConfigService 來接收資料:

Nacos配置中心原理

執行後將列印如下資訊:

Nacos配置中心原理

這裡我用了一個 System.in.read() 方法來監聽輸入的資訊,主要是為了防止主執行緒退出,看不到後續的結果。

修改配置資訊

接下來我們在 Nacos 的控制檯上將我們的配置資訊改為如下圖所示:

Nacos配置中心原理

修改完配置,點選 “釋出” 按鈕後,客戶端將會收到最新的資料,如下圖所示:

Nacos配置中心原理

至此一個簡單的動態配置管理功能已經講完了,刪除配置和更新配置操作類似,這裡不再贅述。

適用場景

瞭解了動態配置管理的效果之後,我們知道了大概的原理了,Nacos 服務端儲存了配置資訊,客戶端連線到服務端之後,根據 dataID,group可以獲取到具體的配置資訊,當服務端的配置發生變更時,客戶端會收到通知。當客戶端拿到變更後的最新配置資訊後,就可以做自己的處理了,這非常有用,所有需要使用配置的場景都可以透過 Nacos 來進行管理。

可以說 Nacos 有很多的適用場景,包括但不限於以下這些情況:

  • 資料庫連線資訊

  • 限流規則和降級開關

  • 流量的動態排程

看過我的 Sentinel 系列文章的同學可能知道,其中有一篇專門介紹叢集限流環境搭建的文章,就是透過 Nacos 來建立動態規則的。

推還是拉

現在我們瞭解了 Nacos 的配置管理的功能了,但是有一個問題我們需要弄明白,那就是 Nacos 客戶端是怎麼實時獲取到 Nacos 服務端的最新資料的。

其實客戶端和服務端之間的資料互動,無外乎兩種情況:

  • 服務端推資料給客戶端

  • 客戶端從服務端拉資料

那到底是推還是拉呢,從 Nacos 客戶端透過 Listener 來接收最新資料的這個做法來看,感覺像是服務端推的資料,但是不能想當然,要想知道答案,最快最準確的方法就是從原始碼中去尋找。

建立 ConfigService

從我們的 demo 中可以知道,首先是建立了一個 ConfigService。而 ConfigService 是透過 ConfigFactory 類建立的,如下圖所示:

Nacos配置中心原理

可以看到實際是透過反射呼叫了 NacosConfigService 的構造方法來建立 ConfigService 的,而且是有一個 Properties 引數的構造方法。

需要注意的是,這裡並沒有透過單例或者快取技術,也就是說每次呼叫都會重新建立一個 ConfigService的例項。

例項化 ConfigService

現在我們來看下 NacosConfigService 的構造方法,看看 ConfigService 是怎麼例項化的,如下圖所示:

Nacos配置中心原理

例項化時主要是初始化了兩個物件,他們分別是:

  • HttpAgent

  • ClientWorker

HttpAgent

其中 agent 是透過裝飾著模式實現的,ServerHttpAgent 是實際工作的類,MetricsHttpAgent 在內部也是呼叫了 ServerHttpAgent 的方法,另外加上了一些統計操作,所以我們只需要關心 ServerHttpAgent 的功能就可以了。

agent 實際是在 ClientWorker 中發揮能力的,下面我們來看下 ClientWorker 類。

ClientWorker

以下是 ClientWorker 的構造方法,如下圖所示:

Nacos配置中心原理

可以看到 ClientWorker 除了將 HttpAgent 維持在自己內部,還建立了兩個執行緒池:

第一個執行緒池是隻擁有一個執行緒用來執行定時任務的 executor,executor 每隔 10ms 就會執行一次 checkConfigInfo() 方法,從方法名上可以知道是每 10 ms 檢查一次配置資訊。

第二個執行緒池是一個普通的執行緒池,從 ThreadFactory 的名稱可以看到這個執行緒池是做長輪詢的。

現在讓我們來看下 executor 每 10ms 執行的方法到底是幹什麼的,如下圖所示:

Nacos配置中心原理

可以看到,checkConfigInfo 方法是取出了一批任務,然後提交給 executorService 執行緒池去執行,執行的任務就是 LongPollingRunnable,每個任務都有一個 taskId。

現在我們來看看 LongPollingRunnable 做了什麼,主要分為兩部分,第一部分是檢查本地的配置資訊,第二部分是獲取服務端的配置資訊然後更新到本地。

1.本地檢查

首先取出與該 taskId 相關的 CacheData,然後對 CacheData 進行檢查,包括本地配置檢查和監聽器的 md5 檢查,本地檢查主要是做一個故障容錯,當服務端掛掉後,Nacos 客戶端可以從本地的檔案系統中獲取相關的配置資訊,如下圖所示:

Nacos配置中心原理

透過跟蹤 checkLocalConfig 方法,可以看到 Nacos 將配置資訊儲存在了

~/nacos/config/fixed-{address}8848nacos/snapshot/DEFAULT_GROUP/{dataId}

這個檔案中,我們看下這個檔案中儲存的內容,如下圖所示:

Nacos配置中心原理

2.服務端檢查

然後透過 checkUpdateDataIds() 方法從服務端獲取那些值發生了變化的 dataId 列表,

透過 getServerConfig 方法,根據 dataId 到服務端獲取最新的配置資訊,接著將最新的配置資訊儲存到 CacheData 中。

最後呼叫 CacheData 的 checkListenerMd5 方法,可以看到該方法在第一部分也被呼叫過,我們需要重點關注一下。

Nacos配置中心原理

可以看到,在該任務的最後,也就是在 finally 中又重新透過 executorService 提交了本任務。

新增 Listener

好了現在我們可以為 ConfigService 來新增一個 Listener 了,最終是呼叫了 ClientWorker 的 addTenantListeners 方法,如下圖所示:

Nacos配置中心原理

該方法分為兩個部分,首先根據 dataId,group 和當前的場景獲取一個 CacheData 物件,然後將當前要新增的 listener 物件新增到 CacheData 中去。

也就是說 listener 最終是被這裡的 CacheData 所持有了,那 listener 的回撥方法 receiveConfigInfo 就應該是在 CacheData 中觸發的。

我們發現 CacheData 是出現頻率非常高的一個類,在 LongPollingRunnable 的任務中,幾乎所有的方法都圍繞著 CacheData 類,現在新增 Listener 的時候,實際上該 Listener 也被委託給了 CacheData,那我們要重點關注下 CacheData 類了。

CacheData

首先讓我們來看一下 CacheData 中的成員變數,如下圖所示:

Nacos配置中心原理

可以看到除了 dataId,group,content,taskId 這些跟配置相關的屬性,還有兩個比較重要的屬性:listeners、md5。

listeners 是該 CacheData 所關聯的所有 listener,不過不是儲存的原始的 Listener 物件,而是包裝後的 ManagerListenerWrap 物件,該物件除了持有 Listener 物件,還持有了一個 lastCallMd5 屬性。

另外一個屬性 md5 就是根據當前物件的 content 計算出來的 md5 值。

觸發回撥

現在我們對 ConfigService 有了大致的瞭解了,現在剩下最後一個重要的問題還沒有答案,那就是 ConfigService 的 Listener 是在什麼時候觸發回撥方法 receiveConfigInfo 的。

現在讓我們回過頭來想一下,在 ClientWorker 中的定時任務中,啟動了一個長輪詢的任務:LongPollingRunnable,該任務多次執行了 cacheData.checkListenerMd5() 方法,那現在就讓我們來看下這個方法到底做了些什麼,如下圖所示:

Nacos配置中心原理

到這裡應該就比較清晰了,該方法會檢查 CacheData 當前的 md5 與 CacheData 持有的所有 Listener 中儲存的 md5 的值是否一致,如果不一致,就執行一個安全的監聽器的通知方法:safeNotifyListener,通知什麼呢?我們可以大膽的猜一下,應該是通知 Listener 的使用者,該 Listener 所關注的配置資訊已經發生改變了。現在讓我們來看一下 safeNotifyListener 方法,如下圖所示:

Nacos配置中心原理

可以看到在 safeNotifyListener 方法中,重點關注下紅框中的三行程式碼:獲取最新的配置資訊,呼叫 Listener 的回撥方法,將最新的配置資訊作為引數傳入,這樣 Listener 的使用者就能接收到變更後的配置資訊了,最後更新 ListenerWrap 的 md5 值。和我們猜測的一樣, Listener 的回撥方法就是在該方法中觸發的。

Md5何時變更

那 CacheData 的 md5 值是何時發生改變的呢?我們可以回想一下,在上面的 LongPollingRunnable 所執行的任務中,在獲取服務端發生變更的配置資訊時,將最新的 content 資料寫入了 CacheData 中,我們可以看下該方法如下:

Nacos配置中心原理

可以看到是在長輪詢的任務中,當服務端配置資訊發生變更時,客戶端將最新的資料獲取下來之後,儲存在了 CacheData 中,同時更新了該 CacheData 的 md5 值,所以當下次執行 checkListenerMd5 方法時,就會發現當前 listener 所持有的 md5 值已經和 CacheData 的 md5 值不一樣了,也就意味著服務端的配置資訊發生改變了,這時就需要將最新的資料通知給 Listener 的持有者。

至此配置中心的完整流程已經分析完畢了,可以發現,Nacos 並不是透過推的方式將服務端最新的配置資訊傳送給客戶端的,而是客戶端維護了一個長輪詢的任務,定時去拉取發生變更的配置資訊,然後將最新的資料推送給 Listener 的持有者。

拉的優勢

客戶端拉取服務端的資料與服務端推送資料給客戶端相比,優勢在哪呢,為什麼 Nacos 不設計成主動推送資料,而是要客戶端去拉取呢?如果用推的方式,服務端需要維持與客戶端的長連線,這樣的話需要耗費大量的資源,並且還需要考慮連線的有效性,例如需要透過心跳來維持兩者之間的連線。而用拉的方式,客戶端只需要透過一個無狀態的 http 請求即可獲取到服務端的資料。

總結

Nacos 服務端建立了相關的配置項後,客戶端就可以進行監聽了。

客戶端是透過一個定時任務來檢查自己監聽的配置項的資料的,一旦服務端的資料發生變化時,客戶端將會獲取到最新的資料,並將最新的資料儲存在一個 CacheData 物件中,然後會重新計算 CacheData 的 md5 屬性的值,此時就會對該 CacheData 所繫結的 Listener 觸發 receiveConfigInfo 回撥。

考慮到服務端故障的問題,客戶端將最新資料獲取後會儲存在本地的 snapshot 檔案中,以後會優先從檔案中獲取配置資訊的值。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69908605/viewspace-2657617/,如需轉載,請註明出處,否則將追究法律責任。