.NET Core 3.0之建立基於Consul的Configuration擴展組件

寫在前面

通過前面三篇關於.NET Core Configuration的文章以後,本篇文章主要討論如何擴展一個Configuration組件出來。若是前面三篇文章沒有看到,能夠點擊以下地址訪問html

瞭解了Configuration的源碼後,再去擴展一個組件就會比較簡單,接下來咱們將在.NET Core 3.0-preview5的基礎上建立一個基於Consul的配置組件。git

相信你們對Consul已經比較瞭解了,不少項目都會使用Consul做爲配置中心,此處也不作其餘闡述了,主要是講一下,建立Consul配置擴展的一些思路。使用Consul配置功能時,咱們能夠將信息轉成JSON格式後再存儲,那麼咱們在讀取的時候,在體驗上就像是從讀取JSON文件中讀取同樣。github

開發前的準備

初始化Consul

假設你已經安裝並啓動了Consul,咱們打開Key/Value功能界面,建立兩組配置選項出來,分別是commonservice和userservice,以下圖所示json

Consul-key-value-dashboard

配置值採用JSON格式緩存

Consul-key-value-commonservice-json

實現思路

咱們知道在Configuration整個的設計框架裏,比較重要的類ConfigurationRoot,內部又有一個IConfigurationProvider集合屬性,也就是說咱們追加IConfigurationProvider實例最終也會被放到到該集合中,以下圖所示框架

rootproviders

該項目中,我使用到了一個已經封裝好的Consul(V0.7.2.6)類庫,同時基於.NET Core關於Configuration的設計風格,作以下的框架設計async

consul

考慮到我會在該組件內部建立ConsulClient實例,因此對ConsulClient構造函數的一部分參數作了抽象提取,並添加到了IConsulConfigurationSource中,以加強該組件的靈活性。ide

以前說過,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實例ui

   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/littlehorse8/Navyblue.Extensions.Configuration.Consul,記得點個小星星哦

相關文章
相關標籤/搜索