欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-20 11:58:32
写在前面 经过前面三篇关于.NET Core Configuration的文章之后,本篇文章主要讨论如何扩展一个Configuration组件出来。如果前面三篇文章没有看到,可以点击如下地址访问 .NET Core 3.0之深入源码理解Configuration(一) .NET Core 3.0之深 ......

写在前面

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

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

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

开发前的准备

初始化consul

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

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

配置值采用json格式

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

实现思路

我们知道在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

   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中所存储的相一致

总结

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

源码已经托管于github,地址:https://github.com/littlehorse8/navyblue.extensions.configuration.consul,记得点个小星星哦