本人主要利用IdentityServer4以及SignalR來實現,IdentityServer4作為認證,SignalR來交互配置,這里一些代碼可能就是部分提出來,主要介紹實現原理及方法
實現配置中心核心的兩個點我們要放在
1、配置文件如何傳送
2、配置文件如何動態的更新
配置文件的傳送結合SignalR來實現
思考:什么樣的客戶端可以來獲取配置?
這里客戶端我們配置了
這里我直接結合Identityserver4,配置客戶端id,客戶端密鑰,配置中心地址、在配置一個IdentityServer4授權地址, 根據這些配置設計下配置中心的 數據庫表,這里直接略
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "ConfigCenter": { "AuthUrl": "http://192.168.0.42:7000", "ClientId": "configclient", "ClientSecret": "configclient", "ServerUrl": "http://localhost:6002" }, "AllowedHosts": "*" }
然后只有手魯SignalR代碼來出來連接消息交互了,不用在去早socket的輪子了,此處了略去,為了准備給自己的服務使用,只有把這個寫成SDK
這里我們需要一個服務類:ClientServices 這個類用來出來 SignalR相關處理 以及配置Json數據的格式話,需要json配置轉換成 .NetCore配置 ,開過配置中的 Data對象的都知道 這個Data中的格式是 {Logging:LogLevel,"1231"}這種鍵值對,怎么來把Json數據處理成為這個數據呢?后面介紹簡便方法
這里對象類需要了解清楚 IConfigruationBuilder、IConfigurationProvider、IConfigurationSource ,這塊不做介紹,而我們要做的就是結合這個來處理我們的配置
建立了三個項目來處理
ConfigCenter:配置服務端API接口,提供給UI 配置管理相關接口
ConfigCenterClient :客戶端配置SDK,提供給服務端連接配置中心提供配置庫
ConfigCenterTest::客戶端服務測試API
配置文件動態更新
首先來開下ConfigurationProvider的源碼
// // 摘要: // Base helper class for implementing an Microsoft.Extensions.Configuration.IConfigurationProvider public abstract class ConfigurationProvider : IConfigurationProvider { // // 摘要: // Initializes a new Microsoft.Extensions.Configuration.IConfigurationProvider protected ConfigurationProvider(); // // 摘要: // The configuration key value pairs for this provider. protected IDictionary<string, string> Data { get; set; } // // 摘要: // Returns the list of keys that this provider has. // // 參數: // earlierKeys: // The earlier keys that other providers contain. // // parentPath: // The path for the parent IConfiguration. // // 返回結果: // The list of keys for this provider. public virtual IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath); // // 摘要: // Returns a Microsoft.Extensions.Primitives.IChangeToken that can be used to listen // when this provider is reloaded. // // 返回結果: // The Microsoft.Extensions.Primitives.IChangeToken. public IChangeToken GetReloadToken(); // // 摘要: // Loads (or reloads) the data for this provider. public virtual void Load(); // // 摘要: // Sets a value for a given key. // // 參數: // key: // The configuration key to set. // // value: // The value to set. public virtual void Set(string key, string value); // // 摘要: // Generates a string representing this provider name and relevant details. // // 返回結果: // The configuration name. public override string ToString(); // // 摘要: // Attempts to find a value with the given key, returns true if one is found, false // otherwise. // // 參數: // key: // The key to lookup. // // value: // The value found at key if one is found. // // 返回結果: // True if key has a value, false otherwise. public virtual bool TryGet(string key, out string value); // // 摘要: // Triggers the reload change token and creates a new one. protected void OnReload();
注意可以看到 Load方法都是虛方法,就是提供給我擴展的,接下來就是關鍵的一步在這個上面處理
構建自己的服務提供類來重寫該方法,然后注入一個事件,提供給外部改變更新的能力
public class LYMConfigProvider : ConfigurationProvider { private ClientServices Client { get; } public LYMConfigProvider(ClientServices clientServices) { Client = clientServices; } private void LoadData(IDictionary<string, string> values) { foreach (var item in values) {
if (item.Key.EndsWith("0"))
{
var likekey= item.Key.Substring(0, item.Key.Length - 1); foreach (var da in Data) { if (da.Key.StartsWith(likekey)) { Data.Remove(da.Key); } } } if (Data.ContainsKey(item.Key)) Data.Remove(item.Key); Data.Add(item.Key, item.Value);
} } public override void Load() { Client.Changed += (arg) => { LoadData(arg.customdata); base.OnReload(); }; var lst = Client.InitClientConfig(); LoadData(lst); } }
通過注冊改變事件,外部就可以通過該事件來實現配置的更新了,值得注意的Load的時候我們需要初始化下配置,畢竟SignalR連接回復初始配置會出現延遲,導致Startup中IConfiguraion初始配置為空
接下來說一個關鍵的問題,就是得到的Json怎么序列化出來到Data中,而Data的類型又是:
// // 摘要: // The configuration key value pairs for this provider. protected IDictionary<string, string> Data { get; set; }
不怕麻煩的朋友可以自己寫遞歸各種轉換,但是那樣太麻煩了,只能用絕招了,通過ConfigurationBuilder對象Build出來的配置提供類都有一個Data,這個Data .NetCode已經幫我們出來好了,於是自己去構建了一個ConfigurationBuilder這樣來處理看行不行?
byte[] by = Encoding.UTF8.GetBytes(json); MemoryStream ms = new MemoryStream(by); var builder = new ConfigurationBuilder().AddJsonStream(ms).Build();
這里調試監控你可以看到Data是有值的,但是你就是取不到,會出現類型轉換錯誤,就算得到Providers強轉自己的類型還是會報錯,且Data在ConfigurationProvider中是protect ,所以為了處理這個問題,我們不得不建立另外的自己的類來擴展下,構建自定義的配置資源以及配置提供類
因為這里是JsonStream,所以我們來擴展JsonStream就ok,這里我們提供了一個公共的GetData方法,來獲取基類中的Data
public class CustomJsonConfigrationProvider : JsonStreamConfigurationProvider { public CustomJsonConfigrationProvider(JsonStreamConfigurationSource jsonStreamConfigurationSource) : base(jsonStreamConfigurationSource) { } public IDictionary<string, string> GetData() { return base.Data; } }
public class CustomJsonConfigrationSource: JsonStreamConfigurationSource { public override IConfigurationProvider Build(IConfigurationBuilder builder) { return new CustomJsonConfigrationProvider(this); } }
所以如何把Json裝換成.netcore中的配置,我們只需要如下代碼即可:
public IDictionary<string, string> BuildDictionary(string json) { byte[] by = Encoding.UTF8.GetBytes(json); MemoryStream ms = new MemoryStream(by); var source = new CustomJsonConfigrationSource(); source.Stream = ms; var builder = new ConfigurationBuilder().Add(source).Build(); var data = builder.Providers.First() as CustomJsonConfigrationProvider; return data.GetData(); }
接下來是校驗成果的時候了,通過引用客戶端SDK,並配置上自己的配置,准備下服務端的初始配置
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigLYMConfiguration() //這里就是我們SDK自己寫的配置擴展方法 .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
這里服務端准備一段配置:{"data":"this the init configuration data"} 以及測試發送配置接口
下面我們通過服務的配置來修改下在刷新頁面:
在環境配置中添加2兩個配置
接下來我們用測試客戶端啟動起來看下,可以看到客戶端實例的信息,以及使用的默認配置
並且訪問下客戶端站點
下面我們服務端啟用Development配置然后刷新客戶端看結果:
實現了針對通過一個客戶端分布式部署統一更新配置,以及獲取連接實例,針對指定服務實例更新配置的功能