配置中心—Consul 配置管理

1. Consul Key/Value 存储

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案对比,Consul 的方案更“一站式”,内置了服务注册与发现框架、分布式一致性协议实现、健康检查、Key/Value 存储(配置中心)、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等),使用起来也较为简单。

请注意这里,Consul 内置了 Key/Value 存储,这里的 Key/Value 存储也可以当作简单的配置中心使用,如果使用了 Consul 作为服务注册发现,不想再额外引入其他中间件的情况下,可以将一些公共配置信息配置到 Consul,然后通过 Consul 提供的 HTTP API 来获取对应 Key 的 Value。

需要注意的是,Consul 遵循ACP原则中的 CP 原则(一致性+分离容忍),保证数据强一致性,所以当数据在同步过程中,或者Leader挂掉 Server 在重新选举 Leader 过程中,会出现集群不可用。

还有一点缺点就是,Consul 不支持配置信息历史版本管理。

通过默认端口 8500 进入 Consul 管理页面,点击左侧的 Key/Value 菜单,可以看到 Consul 的配置信息管理页面

在这里插入图片描述
点击 Create 创建配置信息,这里可以直接创建配置信息,也可以创建文件夹,如果是创建文件夹的话,要以 / 结尾
在这里插入图片描述
我们可以利用多重文件夹的方式,来区分不同微服务、不同应用、不同环境的配置文件。
在这里插入图片描述

2. .Net Core集成 Consul 配置中心

Consul 提供了一系列的 RESTful HTTP API 以供用接入 Consul 的应用对节点、服务、检查、配置等执行基本的 CRUD 操作。

其中,Key/Value 存储相关的 Api 在这里可以看到:KV Store - HTTP API | Consul by HashiCorp

我们可以直接通过 postman 获取到 Consul 中的配置信息:
在这里插入图片描述
其中 value 是配置信息字符串,直接通过 url 获取到的是经过 base64 加密的结果。

而在 .Net Core 中,之前已经讲到,我们并不需要自己基于 http 请求去实现服务相关的操作,只需要引用 Consul.AspNetCore 包即可,里面已经基于官方提供的 RESTful HTTP API 封装好了 Consul 相关操作。

  1. 安装依赖包

    Install-package Consul.AspNetCore
    
  2. 配置 Consul 依赖注入

    在 Starup.cs 中添加 Consul 依赖

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddConsul(Configuration);
    }
    

    services.AddConsul(Configuration); 是自己写的一个扩展,不是 Consul.AspNetCore 中的方法,目的是为了直接从配置文件中读取 Consul 相关配置。

    public static class ConsulServiceCollectionExtensions
    {
        /// <summary>
        /// 向容器中添加Consul必要的依赖注入
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration)
        {
            // 配置consul服务注册信息
            var option = configuration.GetSection("Consul").Get<ConsulOption>();
            // 通过consul提供的注入方式注册consulClient
            services.AddConsul(options => options.Address = new Uri($"http://{option.ConsulIP}:{option.ConsulPort}"));
            return services;
        }
    }
    

    配置信息如下:

    "Consul": {
        "ConsulIP": "192.168.137.200",
        "ConsulPort": "8500",
        "FloderName": "test/service1",
        "FileName": "appsetting-dev"
      }
    

    这里只演示 key/Value 存储的获取和使用,所以就不将当前应用注册到 Consul 中了。

  3. 获取 Consul 中的配置信息

    注册了 ConsulClient 之后,我们就可以通过 IConsulClient 接口对 Consul 集群中的 Key/Value 存储进行操作了。

    [Route("/api/[Controller]")]
    public class ConfigController : ControllerBase
    {
        private readonly IConsulClient _consulClient;
        private readonly IConfiguration _configuration;
        public ConfigController(IConsulClient consulClient,
            IConfiguration configuration)
        {
            _consulClient = consulClient;
            _configuration = configuration;
        }
    
        [HttpGet]
        [Route("")]
        public async Task<string> Get(string key)
        {
            var result = await _consulClient.KV.Get(key);
            if (result.StatusCode != System.Net.HttpStatusCode.OK)
            {
                throw new ConsulRequestException("获取服务信息失败!", result.StatusCode);
            }
            var kvs = result.Response;
            return Encoding.UTF8.GetString(kvs.Value);
        }
    
        [HttpGet]
        [Route("list")]
        public async Task<IList<KVPair>> GetList(string prefix)
        {
            var result = await _consulClient.KV.List(prefix);
            if (result.StatusCode != System.Net.HttpStatusCode.OK)
            {
                throw new ConsulRequestException("获取服务信息失败!", result.StatusCode);
            }
            var kvs = result.Response;
            return kvs.ToList();
        }
    }
    

    在这里插入图片描述

  4. 应用启动时加载 Consul 配置信息,并进行热更新

    通过上面的方式已经可以获取到 Consul Key/Value 存储中的配置信息了,但是我们肯定不希望每次需要使用配置信息的时候这样去获取,而是希望和 .Net Core 中的 Configuration 结合,在启动的时候将配置信息加载到应用中,并且当 Consul 中的配置信息修改时,本地的配置能够更新。

    以下的代码结合 .Net Core 的配置管理机制以及 IConsulClient 实现这样的功能, .Net Core 的配置管理允许我们实现自己的配置提供程序,加入到 IConfigurationBuilder 中,作为我们平常使用的 IConfiguration 的配置来源,所有第三方的配置信息提供程序都是这么实现的。

    (1) 实现 IConfigurationSource,提供 Consul 配置源

    public class ConsulConfigurationSource : IConfigurationSource
    {
        /// <summary>
        /// Consul集群IP
        /// </summary>
        public string ConsulIP { get; set; }
    
        /// <summary>
        /// Consul集群端口
        /// </summary>
        public int ConsulPort { get; set; }
    
        /// <summary>
        /// 配置文件夹名称。类似于命名空间
        /// </summary>
        public string FloderName { get; set; }
    
        /// <summary>
        /// 配置文件名称集合
        /// </summary>
        public string FileName { get; set; }
    
      // 这里需要一个配置信息提供者
        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new ConsulConfigurationProvider(this);
        }
    }
    

    (2) 继承 ConfigurationProvider 实现 ConsulConfigurationProvider

    public class ConsulConfigurationProvider : ConfigurationProvider, IDisposable
    {
       protected readonly ConsulConfigurationSource ConfigurationSource;
       protected readonly ConcurrentDictionary<string, byte[]> ConfigCaches = new ConcurrentDictionary<string, byte[]>();
       protected readonly Dictionary<string, Timer> Timers = new Dictionary<string, Timer>();
       protected readonly IConsulClient _consulClient;
       public ConsulConfigurationProvider(ConsulConfigurationSource configurationSource)
       {
           ConfigurationSource = configurationSource;
           _consulClient = new ConsulClient(x => x.Address = new Uri($"http://{ConfigurationSource.ConsulIP}:{ConfigurationSource.ConsulPort}"));
       }
    
       public override void Load()
       {
           LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();
       }
    
       private async Task LoadAsync()
       {
           var kv = await GetRemoteConfiguration();
           // 记录当前的配置信息
           ConfigCaches[GetConfigKey()] = kv.Value;
           var targetKvs = Flatten(kv);
           
           //将配置的信息转移到Data中,后面的覆盖前面的
           foreach (var item in targetKvs)
           {
               Data[ConfigurationPath.Combine(item.Key.Split("/"))] = item.Value;
           }
    
           // 启动轮询, 监听配置信息的改变
           await ListenToConfigurationChanged();
       }
    
       /// <summary>
       /// 调用 consul 配置中心api, 加载远程配置
       /// </summary>
       /// <returns></returns>
       private async Task<KVPair> GetRemoteConfiguration()
       {
           var targetKeys = string.Join("/", ConfigurationSource.FloderName, ConfigurationSource.FileName);
           var result = await _consulClient.KV.Get(targetKeys);
           if (result.StatusCode != System.Net.HttpStatusCode.OK)
           {
               throw new ConsulRequestException("获取服务信息失败!", result.StatusCode);
           }
    
           var kvs = result.Response;
           return kvs;
       }
    
       /// <summary>
       /// 长轮询,获取远程配置文件的变化
       /// </summary>
       /// <returns></returns>
       private async Task ListenToConfigurationChanged()
       {
           var timer = new Timer(async x =>
           {
               var config = await GetRemoteConfiguration();
               // 和当前缓存的配置新比较
               var configCache = ConfigCaches[GetConfigKey()];
    
               // TODO: 如果两者不同,则重新加载配置信息
               var targetKvs = Flatten(config);
               //将配置的信息转移到Data中,后面的覆盖前面的
               foreach (var item in targetKvs)
               {
                   Data[ConfigurationPath.Combine(item.Key.Split("/"))] = item.Value;
               }
               OnReload();
           }, "", 0, 8000);
           Timers[GetConfigKey()] = timer;
       }
    
       /// <summary>
       /// 处理配置文件
       /// </summary>
       /// <param name="tuple"></param>
       /// <param name="prefixKey"></param>
       /// <returns></returns>
       private IEnumerable<KeyValuePair<string, string>> Flatten(KVPair kv)
       {
           var content = Encoding.UTF8.GetString(kv.Value);
           // 反序列化,将配置文件字符串转换为对象树
           var data = JToken.Parse(content);
           // 通过对象树构建Data
           return Flatten(KeyValuePair.Create(string.Empty, data));
       }
    
       /// <summary>
       /// 递归遍历配置文件,读取json中的每一个键值
       /// </summary>
       /// <param name="tuple"></param>
       /// <param name="prefixKey"></param>
       /// <returns></returns>
       private IEnumerable<KeyValuePair<string, string>> Flatten(KeyValuePair<string, JToken> tuple)
       {
           if (!(tuple.Value is JObject value))
           {
               yield break;
           }
    
           foreach (var property in value)
           {
               var propertyKey = string.IsNullOrEmpty(tuple.Key) ? property.Key : string.Join("/", tuple.Key, property.Key);
               switch(property.Value.Type)
               {
                   case JTokenType.Object:
                       foreach (var item in Flatten(KeyValuePair.Create(propertyKey, property.Value)))
                       {
                           yield return item;
                       }
                       break;
                   case JTokenType.Array:
                       break;
                   default:
                       yield return KeyValuePair.Create(propertyKey, property.Value.Value<string>());
                       break;
               }
           }
       }
    
       /// <summary>
       /// 缓存信息key
       /// </summary>
       /// <returns></returns>
       private string GetConfigKey()
       {
           return $"{ConfigurationSource.FloderName}-{ConfigurationSource.FileName}";
       }
    
       public void Dispose()
       {
           foreach (var timer in Timers)
           {
               timer.Value.Dispose();
           }
       }
    }
    

    (3)提供向 IConfigurationBuilder 添加配置源的扩展方法

    public static class ConsulConfigurationExtensions
    {
        /// <summary>
        /// 通过配置信息,连接consul配置中心,加载配置信息
        /// </summary>
        /// <param name="configurationBuilder"></param>
        /// <returns></returns>
        public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, IConfiguration configuration)
        {
            var configurationSource = new ConsulConfigurationSource();
            configuration.Bind(configurationSource);
            return configurationBuilder.Add(configurationSource);
        }
    
        /// <summary>
        /// 通过配置信息,连接consul配置中心,加载配置信息
        /// </summary>
        /// <param name="configurationBuilder"></param>
        /// <returns></returns>
        public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, Action<ConsulConfigurationSource>  action)
        {
            var configurationSource = new ConsulConfigurationSource();
            action.Invoke(configurationSource);
            return configurationBuilder.Add(configurationSource);
        }
    }
    

    (4)在应用启动的时候添加 Consul 配置源

    public static IHostBuilder CreateHostBuilder(string[] args) =>
              Host.CreateDefaultBuilder(args)
                  .ConfigureAppConfiguration((context, builder) => 
                  { 
                      var config = builder.Build();
                      builder.AddConsul(config.GetSection("Consul"));
                  })
                  .ConfigureWebHostDefaults(webBuilder =>
                  {
                      webBuilder.UseStartup<Startup>();
                  });
    

    (5)通过 IConfiguration 获取 Consul 配置

    [Route("/api/[Controller]")]
     public class ConfigController : ControllerBase
     {
         private readonly IConsulClient _consulClient;
         private readonly IConfiguration _configuration;
         public ConfigController(
             IConsulClient consulClient,
             IConfiguration configuration)
         {
             _consulClient = consulClient;
             _configuration = configuration;
         }[HttpGet]
         [Route("GetConfigByIConfiguration")]
         public string GetConfigByIConfiguration()
         {
             var con = _configuration["AppName"];
             return con;
         }
    }
    

    在这里插入图片描述
    其实上面的这部分 .Net Core 配置管理与 Consul Key/Value 存储集成的实现也可以不用自己写,.Net Core 生态圈中已经有开源的包可以使用,那就是 Winton.Extensions.Configuration.Consul,大家可以通过链接到 github 中了解。

    最基本的使用如下:

    Install-package Winton.Extensions.Configuration.Consul
    
    public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((context, builder) => 
                {
                    //var config = builder.Build();
                    //builder.AddConsul(config.GetSection("Consul"));
                    builder.AddConsul("/test/service1/appsetting-dev", options => 
                    {
                        options.ConsulConfigurationOptions = cco => { cco.Address = new Uri("http://192.168.137.200:8500"); }; // 1、consul地址
                        options.Optional = true; // 2、配置选项
                        options.ReloadOnChange = true; // 3、配置文件更新后重新加载
                        options.OnLoadException = exceptionContext => { exceptionContext.Ignore = true; }; // 4、忽略异常
                    });
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    
    var con = _configuration["test:service1:appsetting-dev:AppName"];
    

微服务系列文章:
上一篇:配置中心—nacos配置中心
下一篇:API网关—Ocelot

Consul 是一个开源的服务发现和配置中心工具,由 HashiCorp 公司开发。它可以帮助应用程序在分布式系统中进行服务发现、故障检测和配置管理。 作为一个配置中心Consul 提供了以下功能: 1. 键值存储:Consul 提供一个分布式的键值存储,可以用来存储应用程序的配置信息。应用程序可以通过 API 或命令行工具来读取和写入配置数据。 2. 服务发现:Consul 可以监测注册在其上的服务的可用性,并提供服务发现功能。应用程序可以通过 Consul 发现和连接到其他服务,从而实现服务之间的通信。 3. 健康检查:Consul 可以定期检查服务的健康状态,并提供报警和自动修复功能。当服务出现故障时,Consul 可以自动将流量路由到健康的实例上。 4. 多数据中心支持:Consul 支持多个数据中心之间的服务发现和配置同步,可以构建全球分布式系统。 使用 Consul 配置中心可以带来许多好处,包括: 1. 集中管理配置:通过将配置信息存储在 Consul 的键值存储中,可以实现集中管理和动态更新配置,减少了手动修改配置文件的工作量。 2. 动态配置更新:Consul 提供了实时变更配置的能力,应用程序可以通过监听配置变更事件来动态更新配置,而不需要重启或重新加载应用程序。 3. 服务发现与负载均衡:Consul 提供了服务发现功能,可以帮助应用程序自动发现和连接其他服务。结合负载均衡,可以实现对服务的高可用和性能优化。 4. 故障检测和自愈能力:Consul 可以定期检查服务的健康状态,并提供故障检测和自愈能力。当服务出现故障时,可以自动切换到健康的实例,提高系统的可用性。 总之,Consul 是一个功能强大的配置中心工具,可以帮助构建可靠的分布式系统,并简化配置管理和服务发现的工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值