配置中心—Consul配置管理
配置中心—Consul配置管理Consul Key/Value存储.Net Core集成Consul配置中心Consul Key/Value存储Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框架、分布式一致性协议实现、健康检查、Key/Value 存储(配置中心)
配置中心—Consul配置管理
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创建配置信息,这里可以直接创建配置信息,也可以创建文件夹,如果是创建文件夹的话,要以 / 结尾
我们可以利用多重文件夹的方式,来区分不同微服务、不同应用、不同环境的配置文件。
.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相关操作。
-
安装依赖包
Install-package Consul.AspNetCore
-
配置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中了。
-
获取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(); } }
-
应用启动时加载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) 继承 ConsulConfigurationProvider 实现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
更多推荐
所有评论(0)