前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET Core 3.0之创建基于Consul的Configuration扩展组件

.NET Core 3.0之创建基于Consul的Configuration扩展组件

作者头像
Edison.Ma
发布2019-07-19 17:53:39
6440
发布2019-07-19 17:53:39
举报
文章被收录于专栏:DotNet Core圈圈DotNet Core圈圈

写在前面

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

  • .NET Core 3.0之深入源码理解Configuration(一)
  • .NET Core 3.0之深入源码理解Configuration(二)
  • .NET Core 3.0之深入源码理解Configuration(三)

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

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

开发前的准备

初始化Consul

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

配置值采用JSON格式

实现思路

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

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

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

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

核心代码

IConsulConfigurationSource

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

ConsulConfigurationSource

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

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

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

代码语言:javascript
复制
   1:  public IConfigurationProvider Build(IConfigurationBuilder builder)
代码语言:javascript
复制
   2:  {
代码语言:javascript
复制
   3:      ConsulConfigurationParser consulParser = new ConsulConfigurationParser(this);
代码语言:javascript
复制
   4:   
代码语言:javascript
复制
   5:      return new ConsulConfigurationProvider(this, consulParser);
代码语言:javascript
复制
   6:  }

ConsulConfigurationParser

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

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

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

ConsulConfigurationProvider

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

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

调用及运行结果

此处调用在Program中实现

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

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

总结

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet技术平台 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 开发前的准备
    • 初始化Consul
      • 实现思路
      • 核心代码
        • IConsulConfigurationSource
          • ConsulConfigurationSource
            • ConsulConfigurationParser
              • ConsulConfigurationProvider
              • 调用及运行结果
              • 总结
              相关产品与服务
              对象存储
              对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档