.NetCore下构建自己的服务配置中心-手动造轮子

本人主要利用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配置然后刷新客户端看结果:

 

实现了针对通过一个客户端分布式部署统一更新配置,以及获取连接实例,针对指定服务实例更新配置的功能

参考文献:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#custom-configuration-provider

原文地址:https://www.cnblogs.com/liyouming/p/13415171.html