zoukankan      html  css  js  c++  java
  • .NET Core 3.0之创建基于Consul的Configuration扩展组件

    写在前面

    经过前面三篇关于.NET Core Configuration的文章之后,本篇文章主要讨论如何扩展一个Configuration组件出来。如果前面三篇文章没有看到,可以点击如下地址访问

    了解了Configuration的源码后,再去扩展一个组件就会比较简单,接下来我们将在.NET Core 3.0-preview5的基础上创建一个基于Consul的配置组件。

    相信大家对Consul已经比较了解了,很多项目都会使用Consul作为配置中心,此处也不做其他阐述了,主要是讲一下,创建Consul配置扩展的一些思路。使用Consul配置功能时,我们可以将信息转成JSON格式后再存储,那么我们在读取的时候,在体验上就像是从读取JSON文件中读取一样。

    开发前的准备

    初始化Consul

    假设你已经安装并启动了Consul,我们打开Key/Value功能界面,创建两组配置选项出来,分别是commonservice和userservice,如下图所示

    Consul-key-value-dashboard

    配置值采用JSON格式

    Consul-key-value-commonservice-json

    实现思路

    我们知道在Configuration整个的设计框架里,比较重要的类ConfigurationRoot,内部又有一个IConfigurationProvider集合属性,也就是说我们追加IConfigurationProvider实例最终也会被放到到该集合中,如下图所示

    rootproviders

    该项目中,我使用到了一个已经封装好的Consul(V0.7.2.6)类库,同时基于.NET Core关于Configuration的设计风格,做如下的框架设计

    consul

    考虑到我会在该组件内部创建ConsulClient实例,所以对ConsulClient构造函数的一部分参数做了抽象提取,并添加到了IConsulConfigurationSource中,以增强该组件的灵活性。

    之前说过,Consul中的配置信息是以JSON格式存储的,所以此处使用到了Microsoft.Extensions.Configuration.Json.JsonConfigurationFileParser,用以将JSON格式的信息转换为Configuration的通用格式Key/Value。

    核心代码

    IConsulConfigurationSource

       1:  /// <summary>
       2:  /// ConsulConfigurationSource
       3:  /// </summary>
       4:  public interface IConsulConfigurationSource : IConfigurationSource
       5:  {
       6:      /// <summary>
       7:      /// CancellationToken
       8:      /// </summary>
       9:      CancellationToken CancellationToken { get; }
      10:   
      11:      /// <summary>
      12:      /// Consul构造函数实例,可自定义传入
      13:      /// </summary>
      14:      Action<ConsulClientConfiguration> ConsulClientConfiguration { get; set; }
      15:   
      16:      /// <summary>
      17:      ///  Consul构造函数实例,可自定义传入
      18:      /// </summary>
      19:      Action<HttpClient> ConsulHttpClient { get; set; }
      20:   
      21:      /// <summary>
      22:      ///  Consul构造函数实例,可自定义传入
      23:      /// </summary>
      24:      Action<HttpClientHandler> ConsulHttpClientHandler { get; set; }
      25:   
      26:      /// <summary>
      27:      /// 服务名称
      28:      /// </summary>
      29:      string ServiceKey { get; }
      30:   
      31:      /// <summary>
      32:      /// 可选项
      33:      /// </summary>
      34:      bool Optional { get; set; }
      35:   
      36:      /// <summary>
      37:      /// Consul查询选项
      38:      /// </summary>
      39:      QueryOptions QueryOptions { get; set; }
      40:   
      41:      /// <summary>
      42:      /// 重新加载延迟时间,单位是毫秒
      43:      /// </summary>
      44:      int ReloadDelay { get; set; }
      45:   
      46:      /// <summary>
      47:      /// 是否在配置改变的时候重新加载
      48:      /// </summary>
      49:      bool ReloadOnChange { get; set; }
      50:  }

    ConsulConfigurationSource

    该类提供了一个构造函数,用于接收ServiceKey和CancellationToken实例

       1:  public ConsulConfigurationSource(string serviceKey, CancellationToken cancellationToken)
       2:  {
       3:      if (string.IsNullOrWhiteSpace(serviceKey))
       4:      {
       5:          throw new ArgumentNullException(nameof(serviceKey));
       6:      }
       7:   
       8:      this.ServiceKey = serviceKey;
       9:      this.CancellationToken = cancellationToken;
      10:  }

    其build()方法也比较简单,主要是初始化ConsulConfigurationParser实例

       1:  public IConfigurationProvider Build(IConfigurationBuilder builder)
       2:  {
       3:      ConsulConfigurationParser consulParser = new ConsulConfigurationParser(this);
       4:   
       5:      return new ConsulConfigurationProvider(this, consulParser);
       6:  }

    ConsulConfigurationParser

    该类比较复杂,主要实现Consul配置的获取、监控以及容错处理,公共方法源码如下

       1:  /// <summary>
       2:  /// 获取并转换Consul配置信息
       3:  /// </summary>
       4:  /// <param name="reloading"></param>
       5:  /// <param name="source"></param>
       6:  /// <returns></returns>
       7:  public async Task<IDictionary<string, string>> GetConfig(bool reloading, IConsulConfigurationSource source)
       8:  {
       9:      try
      10:      {
      11:          QueryResult<KVPair> kvPair = await this.GetKvPairs(source.ServiceKey, source.QueryOptions, source.CancellationToken).ConfigureAwait(false);
      12:          if ((kvPair?.Response == null) && !source.Optional)
      13:          {
      14:              if (!reloading)
      15:              {
      16:                  throw new FormatException(Resources.Error_InvalidService(source.ServiceKey));
      17:              }
      18:   
      19:              return new Dictionary<string, string>();
      20:          }
      21:   
      22:          if (kvPair?.Response == null)
      23:          {
      24:              throw new FormatException(Resources.Error_ValueNotExist(source.ServiceKey));
      25:          }
      26:   
      27:          this.UpdateLastIndex(kvPair);
      28:   
      29:          return JsonConfigurationFileParser.Parse(source.ServiceKey, new MemoryStream(kvPair.Response.Value));
      30:      }
      31:      catch (Exception exception)
      32:      {
      33:          throw exception;
      34:      }
      35:  }
      36:   
      37:  /// <summary>
      38:  /// Consul配置信息监控
      39:  /// </summary>
      40:  /// <param name="key"></param>
      41:  /// <param name="cancellationToken"></param>
      42:  /// <returns></returns>
      43:  public IChangeToken Watch(string key, CancellationToken cancellationToken)
      44:  {
      45:      Task.Run(() => this.RefreshForChanges(key, cancellationToken), cancellationToken);
      46:   
      47:      return this.reloadToken;
      48:  }

    另外,关于Consul的监控主要利用了QueryResult.LastIndex属性,该类缓存了该属性的值,并与实获取的值进行比较,以判断是否需要重新加载内存中的缓存配置

    ConsulConfigurationProvider

    该类除了实现Load方法外,还会根据ReloadOnChange属性,在构造函数中注册OnChange事件,用于重新加载配置信息,源码如下:

       1:  public sealed class ConsulConfigurationProvider : ConfigurationProvider
       2:  {
       3:      private readonly ConsulConfigurationParser configurationParser;
       4:      private readonly IConsulConfigurationSource source;
       5:   
       6:      public ConsulConfigurationProvider(IConsulConfigurationSource source, ConsulConfigurationParser configurationParser)
       7:      {
       8:          this.configurationParser = configurationParser;
       9:          this.source = source;
      10:   
      11:          if (source.ReloadOnChange)
      12:          {
      13:              ChangeToken.OnChange(
      14:                  () => this.configurationParser.Watch(this.source.ServiceKey, this.source.CancellationToken),
      15:                  async () =>
      16:                  {
      17:                      await this.configurationParser.GetConfig(true, source).ConfigureAwait(false);
      18:   
      19:                      Thread.Sleep(source.ReloadDelay);
      20:   
      21:                      this.OnReload();
      22:                  });
      23:          }
      24:      }
      25:   
      26:      public override void Load()
      27:      {
      28:          try
      29:          {
      30:              this.Data = this.configurationParser.GetConfig(false, this.source).ConfigureAwait(false).GetAwaiter().GetResult();
      31:          }
      32:          catch (AggregateException aggregateException)
      33:          {
      34:              throw aggregateException.InnerException;
      35:          }
      36:      }
      37:  }

    调用及运行结果

    此处调用在Program中实现

       1:  public class Program
       2:  {
       3:      public static void Main(string[] args)
       4:      {
       5:          CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
       6:   
       7:          WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration(
       8:              (hostingContext, builder) =>
       9:              {
      10:                  builder.AddConsul("userservice", cancellationTokenSource.Token, source =>
      11:                  {
      12:                      source.ConsulClientConfiguration = cco => cco.Address = new Uri("http://localhost:8500");
      13:                      source.Optional = true;
      14:                      source.ReloadOnChange = true;
      15:                      source.ReloadDelay = 300;
      16:                      source.QueryOptions = new QueryOptions
      17:                      {
      18:                          WaitIndex = 0
      19:                      };
      20:                  });
      21:   
      22:                  builder.AddConsul("commonservice", cancellationTokenSource.Token, source =>
      23:                  {
      24:                      source.ConsulClientConfiguration = cco => cco.Address = new Uri("http://localhost:8500");
      25:                      source.Optional = true;
      26:                      source.ReloadOnChange = true;
      27:                      source.ReloadDelay = 300;
      28:                      source.QueryOptions = new QueryOptions
      29:                      {
      30:                          WaitIndex = 0
      31:                      };
      32:                  });
      33:              }).UseStartup<Startup>().Build().Run();
      34:      }
      35:  }

    运行结果,如下图所示,我们已经加载到了两个ConsulProvider实例,这与我们在Program中添加的两个Consul配置一致,其中所加载到的值也和.NET Core Configuration的Key/Value风格相一致,所加载到的值也会Consul中所存储的相一致

    image

    image

    image

    总结

    基于源码扩展一个配置组件出来,还是比较简单的,另外需要说明的是,该组件关于JSON的处理主要基于.NET Core原生类库,位于命名空间内的System.Text.Json中,所以该组件无法在.NET Core 3.0之前的版本中运行,需要引入额外的JSON组件辅助处理。

    源码已经托管于GitHub,地址:https://github.com/edison0621/Navyblue.Extensions.Configuration.Consul,记得点个小星星哦

  • 相关阅读:
    【转】Android Touch事件传递机制解析
    通过Selector来设置按钮enable/unable状态的样式
    Android中的selector
    Android单元测试
    Android Lint简介
    制作高仿QQ的聊天系统(下)—— Adapter & Activity
    EditText的监听器和自定义回车事件
    监听Listview的滚动状态,是否滚动到了顶部或底部
    制作高仿QQ的聊天系统(上)—— 布局文件 & 减少过度绘制
    数据更新后让ListView自动滚动到底部
  • 原文地址:https://www.cnblogs.com/edison0621/p/10922281.html
Copyright © 2011-2022 走看看