本人主要利用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 (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配置然后刷新客户端看结果:
实现了针对通过一个客户端分布式部署统一更新配置,以及获取连接实例,针对指定服务实例更新配置的功能