擼了一個簡易的配置中心,順帶整合到了SpringCloud

来源:https://www.cnblogs.com/zzyang/archive/2022/10/14/16791296.html
-Advertisement-
Play Games

大家好,我是三友~~ 最近突然心血來潮(就是閑的)就想著擼一個簡單的配置中心,順便也照葫蘆畫瓢給整合到SpringCloud。 本文大綱 配置中心的概述 隨著歷史的車輪不斷的前進,技術不斷的進步,單體架構的系統都逐漸轉向微服務架構。雖然微服務架構有諸多優點,但是隨著越來越多的服務實例的數量,配置的不 ...


大家好,我是三友~~

最近突然心血來潮(就是閑的)就想著擼一個簡單的配置中心,順便也照葫蘆畫瓢給整合到SpringCloud。

本文大綱

配置中心的概述

隨著歷史的車輪不斷的前進,技術不斷的進步,單體架構的系統都逐漸轉向微服務架構。雖然微服務架構有諸多優點,但是隨著越來越多的服務實例的數量,配置的不斷增多,傳統的配置文件方式不能再繼續適用業務的發展,所以急需一種可以統一管理配置文件應用,在此之下配置中心就誕生了。

所以配置中心就是用來統一管理各種服務配置的一個組件,本質上就是一個web應用。

配置中心的核心功能

一個配置中心的核心功能其實主要包括兩個:

  • 配置的存取
  • 配置變更的通知

配置的存取是配置中心不可缺失的功能,配置中心需要能夠將配置進行保存,存在磁碟文件也好,又或是資料庫也罷,總之需要持久化,同時配置中心也得提供配置查詢的功能。

配置變化的通知也是一個很重要的功能,一旦配置中心的配置有變動的話,那麼使用到這個配置的客戶端就需要知道這個配置有變動,從而可以做到相應的變動的操作。

手擼一個簡易的配置中心

上文分析了一個配置中心的核心功能,接下來就實現這兩個核心的功能。

一、文件工程整體分析

文件工程整體分為客戶端與服務端

  • 服務端:單獨部署的一個web應用,埠是8888,提供了對於配置增刪改查的http介面
  • 客戶端(SDK):業務系統需要引用對應的依賴,封裝了跟服務端交互的代碼

二、服務端實現詳解

1、配置文件的數據存儲模型ConfigFile

在配置中心存儲配置的時候,需要指明以下信息

public class ConfigFile {

    private String fileId;

    private String name;

    private String extension;

    private String content;

    private Long lastUpdateTimestamp;

}
  • fileId: 文件的唯一id,由配置中心服務端在新增配置文件存儲的時候自動生成,全局唯一
  • name: 就是文件的名字,沒有什麼要求,見名知意就行
  • extension: 文件尾碼名,指的是該配置是什麼類型的文件,比如是properties、yml等
  • content: 就是配置文件的內容,不同的尾碼名有不同的格式要求
  • lastUpdateTimestamp: 上一次文件更新的時間戳。當文件存儲或者更新的時候,需要更新時間戳,這個欄位是用來判斷文件是否有改動
2、文件存儲層ConfigFileStorage

對於文件存儲層,我提供了一個ConfigFileStorage介面,

public interface ConfigFileStorage {

    void save(ConfigFile configFile);

    void update(ConfigFile configFile);

    void delete(String fileId);

    ConfigFile selectByFileId(String fileId);

    List<ConfigFile> selectAll();

}

這個介面提供了對於配置存儲的crud操作,目前我已經實現了基於記憶體和磁碟文件的存儲的代碼

可以在項目啟動的時候,在配置文件指定是基於磁碟文件存儲還是基於記憶體存儲,預設是基於磁碟文件存儲。

當然,如果想把配置信息存儲到資料庫,只要新增一個存儲到數據的實現就行。

3、ConfigController

ConfigController提供了對於配置文件的crud的http介面

ConfigController是通過調用ConfigManager來完成配置文件的crud

4、ConfigManager

其實就是一個service層,就是簡單的參數封裝,最終是調用ConfigFileStorage存儲層的實現來完成配置的存儲功能。

這樣配置中心的配的存取的功能就實現了。

所以,服務端還是比較簡單的。其實就是跟平時寫的業務系統的crud沒什麼區別,就是將資料庫存儲替換成了磁碟文件的存儲。

至於前面說的配置文件變更通知的功能,我是基於客戶端來實現的。

三、客戶端的實現

客戶端工程代碼如下

1、ConfigFileChangedListener
ConfigFileChangedListener
ConfigFileChangedListener

配置變動的監聽器,當客戶端對某個配置監聽的時候,如果這個配置的內容有變化的話,客戶端就會回調這個監聽器,傳入最新的配置

2、ConfigService

封裝了客戶端的核心功能,可以添加對某個文件的監聽器和獲取某個文件的配置內容。

使用示例:

// 創建一個ConfigService,傳入配置中心服務端的地址
ConfigService configService = new ConfigService("localhost:8888");

// 從服務端獲取配置文件的內容,文件的id是新增配置文件時候自動生成
ConfigFile config = configService.getConfig("69af6110-31e4-4cb4-8c03-8687cf012b77");

// 對某個配置文件進行監聽
configService.addListener("69af6110-31e4-4cb4-8c03-8687cf012b77"new ConfigFileChangedListener() {
    @Override
    public void onFileChanged(ConfigFile configFile) {
        System.out.printf("fileId=%s配置文件有變動,最新內容為:%s%n", configFile.getFileId(), configFile.getContent());
    }
});

這裡說一下配置變更通知的實現原理。

首先對於客戶端來說,要想知道哪個配置文件進行了改動,有兩種方式

第一種是通過push的方式來實現。當配置文件發生變動的時候,服務端主動將變動的配置文件push給客戶端。這種方式實現起來比較麻煩,一方面是服務端還得存儲客戶端的服務的信息,因為服務端得知道push到哪台伺服器上;另一方面,客戶端需要提供一個介面來接收服務端push的請求,所以這種方式整體實現起來比較麻煩。但是這種push方式時實性比較好,一旦配置文件有變動,第一時間客戶端就能夠知道配置有變動。

第二種方式就是基於pull模式來實現。客戶端定時主動去服務端拉取配置文件,判斷文件內容是否有變動,一旦有變動就進行監聽器的回調。這種實現相比push來說簡單不少,因為服務端不需要關心客戶端的信息,所有的操作都由客戶端來完成。但是這個定時的時間間隔不好控制,太長可能會導致時實性差,太短會導致可能無效請求過多,因為配置壓根可能沒有變化。

但是這裡我選擇了第二種方式,因為實現起來簡單。。

變動通知代碼實現
變動通知代碼實現

到這,一個簡單的配置中心的服務端的和客戶端就完成了,這裡畫張圖來總結一下配置中心的核心原理。

接下來就把這個簡易的配置中心整合到SpringCloud中。

SpringCloud配置中心的原理

1、項目啟動是如何從配置中心載入數據的?

在SpringCloud環境下,當項目啟動的時候,在SpringBoot應用容器創建之前,會先創建一個容器,這個容器非常重要,這個容器是用來跟配置中心交互,拉取配置的。

這個容器在啟動的時候會幹兩件事:

  • 載入bootstrap配置文件,這就是為什麼配置中心的配置信息需要寫在bootstrap配置文件的重要原因

  • 載入所有spring.factories文件中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration對應的配置類,將這些配置類註入到這個容器中,註意這裡是不會載入@EnbaleAutoConfiguration自動裝配的類

當這兩件事都做好之後,會從這個容器中獲取到所有的PropertySourceLocator這個介面的實現類對象,依次調用locate方法。

PropertySourceLocator
PropertySourceLocator

這個類很重要,先來看看註釋

Strategy for locating (possibly remote) property sources for the Environment. Implementations should not fail unless they intend to prevent the application from starting.

扔到有道翻譯如下:

為環境定位(可能是遠程)屬性源的策略。實現不應該失敗,除非它們打算阻止應用程式啟動。

說的簡單點就是用來定位到(也就是獲取的意思)項目啟動所需要的屬性信息。同時要註意到括弧內的 可能是遠程 告訴我們一個很重要的信息,那就是獲取的配置信息不僅僅可以存在本地,而且還可以存在遠程。

遠程?作者這裡就差直接告訴你可以從配置中心獲取了。。

所以從這個註釋就可以發現,原來PropertySourceLocator就是起到在SpringCloud環境下從配置中心獲取配置的作用。

PropertySourceLocator是一個介面,所以只要不同的配置中心實現這個介面,那麼不同的配置中心就可以整合到了SpringCloud,從而實現從配置中心載入配置屬性到Spring環境中了。

2、如何實現註入到Bean中的屬性動態刷新?

上面講了在項目啟動的時候SpringCloud是如何從配置中心載入數據的,主要是通過新建一個容器,載入bootstrap配置文件和一些配置類,最後會調用PropertySourceLocator來從配置中心獲取到配置信息。

那麼在SpringCloud環境下,是如何實現註入到Bean中的屬性動態刷新的呢?

舉個例子

UserService
UserService

當在類上加一個@RefreshScope註解之後,那麼當配置中心sanyou.username的屬性有變化的時候,那麼此時註入的username也會跟著變化。

這種變化是如何實現的呢?

SpringCloud中規定,當配置中心客戶端一旦感知到服務端的某個配置有變化的時候,需要發佈一個RefreshEvent事件來告訴SpringCloud配置有變動。

在SpringCloud中RefreshEventListener類會去監聽這個事件,一旦監聽到這個事件,就會進行兩步操作來刷新註入到對象的屬性。

RefreshEventListener
RefreshEventListener
  • 從配置中心再次拉取屬性值,而這個拉取的代碼邏輯跟項目啟動時拉取的屬性值核心邏輯幾乎是一樣的,也是創建一個新的spring容器,載入配置文件和配置類,最後通過PropertySourceLocator獲取屬性,這一部分核心的代碼邏輯是復用的。

  • 有了最新的屬性之後,就開始刷新對象的屬性。

刷新的邏輯實現的非常的巧妙,可不是你以為的簡單地將新的屬性重新註入對象中,而是通過動態代理的方式來實現的。

對於在類上加了@RefreshScope註解的Bean,Spring在生成這個Bean的時候,會進行動態代理。

這裡我們就上面舉個UserService例子來分析,在生成UserService有兩步操作

  • 生成一個UserService對象,將從配置中心拉到的配置sanyou.username註入給UserService對象

  • 由於加了@RefreshScope,會給上一步驟生成的UserService對象進行代理,生成一個代理對象

最後真正暴露出去供我們使用的其實是就是這個代理對象,如圖所示

由於暴露出去的是一個代理對象,所以當調用getUsername方法的時候,其實是調用UserService的代理對象的getUsername方法,從而就會找到UserService,調用UserService的getUsername獲取到username的屬性值。

當配置中心的配置有變動刷新屬性的時候,Spring會把UserService這個對象(非代理對象)給銷毀,重新創建一個UserService對象,註入最新的屬性值。

當再次通過UserService代理對象獲取username屬性的時候,就會找最新創建的那個UserService對象,此時就能獲取到最新的屬性值。

配置每刷新一次,UserService對象就會先銷毀再重新創建,但是暴露出去的UserService代理對象一直不會變。

這樣,對於使用者來說,好像是UserService對象的屬性自動刷新了,其實本質上是UserService代理對象最終找的UserService對象發生了變化。

到這應該就知道為什麼加了@RefreshScope的對象能夠實現配置的自動刷新了,其實依靠的是動態代理完成的。

3、源碼執行流程圖

由於上面並沒有涉及整體執行流程的源碼分析,所以我特地結合源碼畫了兩張源碼的執行流程圖,有興趣的小伙伴可以對照著圖翻一翻具體的源碼。

3.1啟動時載入配置流程

最終從配置中心獲取到的屬性會放在項目啟動時創建的 Environment 對象裡面。

3.2配置刷新源碼流程

這個圖新增了對於加了@ConfigurationProperties數據綁定的對象原理的分析。

整合SpringCloud和測試

一、整合SpringCloud

1、ConfigCenterProperties

配置中心的配置信息,這裡需要配置配置中心服務端的地址和使用的配置文件的id。當然這部分信息需要寫在bootstrap配置文件中,前面也說過具體的原因。

2、ConfigCenterPropertySourceLocator

上面分析知道,項目啟動和刷新的時候,SpringCloud是通過PropertySourceLocator的實現從配置中心載入配置信息,所以這裡就得實現一下

核心的邏輯就是根據所配置的文件的id,從配置中心拉取配置信息,然後解析配置。

3、ConfigContextRefresher

這個是用來註冊文件變動的監聽器,來刷新文件的信息的。

因為上面提到,當配置發生變化的時候,需要發佈一個RefreshEvent事件來觸發刷新配置的功能。

核心的邏輯就是當項目啟動的時候,對所使用的配置文件進行註冊一個監聽器,監聽器的實現就是當發生配置改動的時候,就發佈一個RefreshEvent事件。

4、兩個配置類
4.1 ConfigCenterBootstrapConfiguration

配置了ConfigCenterPropertySourceLocator、ConfigCenterProperties、ConfigService

4.2 ConfigCenterAutoConfiguration

配置了ConfigContextRefresher、ConfigCenterProperties、ConfigService

最後需要將兩個配置類在spring.factories配置一下。

這裡有個需要註意,前面說過,SpringCloud會創建新的容器來載入配置,而這個容器只會載入spring.factories文件中鍵為@BootstrapConfiguration註解的配置類,所以需要將ConfigCenterBootstrapConfiguration跟BootstrapConfiguration配對,因為ConfigCenterBootstrapConfiguration配置了ConfigCenterPropertySourceLocator。

好了,到這裡真的就完成了對SpringCloud整合了。

二、測試

1、新增一個配置文件

啟動配置中心的server端,然後打開ApiPost,新增一個配置文件

新增文件類型為properties一個配置,內容為sanyou.username=sanyou鍵值對,當然可以寫很多鍵值對,我這裡就寫了一個,新增成功之後,返回了文件的id:79765c73-c1ef-4ea2-ba77-5d27a64c4685

2、測試客戶端

這裡我為了方便,就把測試代碼跟客戶端寫在同一個服務了,正常情況肯定是把跟SpringCloud代碼打成一個依賴引到項目中。

在bootstrap.yml文件中配置配置中心的相關信息

  • 配置中心服務端的地址是:localhost:8888
  • 使用的配置文件的id是剛纔創建的:79765c73-c1ef-4ea2-ba77-5d27a64c4685

測試Controller

提供一個介面,註入上面提到的UserService

啟動項目,調用介面

從斷這裡可以看出,實際註入的是一個UserService代理對象,並且最終找的是com.sanyou.configcenter.test.UserService@3a1e4fd3這個UserService對象

此時這次調用的返回值就是:sanyou

接下來測試一下自動刷新屬性的功能

現在修改一下配置中心的sanyou.username為sanyou666

靜靜等待5秒鐘。。

此時控制台列印出 Refresh keys changed: [sanyou.username] ,也就是sanyou.username屬性變了

此時再次獲取username

可以看出,UserService代理對象沒變,但是UserService對象已經變成了com.sanyou.configcenter.test.UserService@4237b3cd

此時獲取到的username就已經變成了sanyou666

所以,到這裡就成功將我們自己寫的那個簡易版的配置中心整合到了SpringCloud中了。

不足和改進

雖然我們這裡的配置中心有了配置中心基本的功能,但是其實還有很多的不足和可以改進的地方。

1、配置變更推送問題

問題前面也說過,在判斷配置是否變更的時候,這裡是每隔5s從服務端獲取一次,這裡就會可能5s之後才能感知到配置有變化,達不到真正時實的效果,並且由於這裡是由客戶端根據來判斷,會導致無效的請求過多,因為可能配置壓根沒有變化,但是還是每隔5s獲取一次配置信息,白白浪費資源

解決這個問題可以換成上面提到的push方式來做,或者將輪詢方式改成長輪詢的方式實現也是可以的,如果不清楚push、輪詢、長輪詢的,可以翻一下

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 嗨嘍,大家好呀~這裡是愛看美女的茜茜吶 又到了學Python時刻~ 有時候下班~忙著跑路,忘記關電腦,逮到了會被老闆扣工資!!怎麼辦? python帶你製做一個小程式,到點自動關機~ 再也不怕忘關電腦啦~ 還能再嘗試的時候順帶摸摸魚,被老闆發現就是再調試程式~hhhhh 用python寫一個自 ...
  • ODS(OpenDocument Spreadsheet)是一種基於XML的文件格式,可以使用OpenOffice.org的Calc組件打開和建立。與MS Excel文件類似,ODS文件將數據存儲在組織成行和列的單元格中,並可以包含文本、數學函數、格式化等內容。 ...
  • 好家伙,上一篇做出來問題多多, 問題太多了,包括但不限於前端報錯:1.超出調用棧 2.跨域錯誤 vue3確實有很多我不熟悉的地方 所以,我們用回vue2吧 這裡全部用回之前的方法 Springboot連接資料庫 - 養肥胖虎 - 博客園 (cnblogs.com) 1、去新建一個spring boo ...
  • 緩存就是記憶體中的數據,常常來自對資料庫查詢結果的保存。使用緩存,我們可以避免頻繁的與資料庫進行交互,進而提高響應速度MyBatis也提供了對緩存的支持,分為一級緩存和二級緩存,可以通過下圖來理解: ①、一級緩存是SqlSession級別的緩存。在操作資料庫時需要構造sqlSession對象,在對象中 ...
  • AttribteError: ‘module’ object has no attribute xxx’ 描述:模塊沒有相關屬性。可能出現的原因:1.命名.py文件時,使用了Python保留字或者與模塊名等相同。解決:修改文件名2…pyc文件中緩存了沒有更新的代碼。解決:刪除該庫的.pyc 文件 A ...
  • 某寶秒殺,用毫秒級的精準度來搶購! 還記得前段時間情人節,各種產品活動秒殺。結果自然少不了被對象一番折磨 (註意:不是new出來的哈,也不是橡膠的,實實在在的女朋友) 於是乎徹底激發了我的求生欲,在這種關頭我是必鬚髮揮出自己的才能了,這才有了這篇毫秒級秒殺的精品出來,話不多說直接進入主題 項目環境​ ...
  • 2022-10-11 10:58:41 😀 前言 本文開始流程式控制制方面的學習,主要包括用戶交互和流程式控制制語句,適合新手學習。 1 用戶交互Scanner 1.1 Scanner對象 Java提供了一個可以獲取用戶輸入的Scanner工具類 基本語法: Scanner s = new Scanner ...
  • 各種語言用到的編輯器 python開發:pycharm(收費),vscode(免費),sublintext, go開發:goland(收費),vscode,國產的 java:idea(收費),eclipse(免費),MyEclipse(收費) android:androidstudio(免費),ec ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...