.net core consul watch 监听 服务的变化 获取刷新的节点 做到实时获取最新节点
在 使用consul 时 发现每次 获取节点 需要连接 花费时间 较长 所以优化 放到 内存中 放到内存中比 redis 缓存中要快很多
那么问题来了 多个服务 怎么做到 统一 更新最新节点 用到Cap 事件总线的 发布订阅的方式 让 各个节点 收到刷新消息
原理 就是 利用 cap 发布订阅的功能 做到实时更新 consul 最新的节点信息
参考 ASP.NET CORE 使用Consul实现服务治理与健康检查(1)——概念篇 | 码农家园 (codenong.com)
通知机制——Consul Watch
Consul 可以通过配置 Agent 对以下类型的数据进行监控,并且同样受反熵机制的影响,如果想监控集群下所有服务,那么需要将监控配置放在服务端:
- key – 监视指定K/V键值对
- keyprefix – Watch a prefix in the KV store
- services – 监视服务列表
- nodes – 监控节点列表
- service – 监视服务实例
- checks- 监视健康检查的值
- event – 监视用户事件
Consul 主要提供2种通知方式:
- script:当发生变化时执行一段脚本(可以是放在服务器中的任何可执行脚本,例如 py sh 等)
- HTTP endpoint:当发生变化时请求配置的http地址
例如在 Consul 配置文件创建 watch.json ,重启 Consul 后生效
consul 新建文件
内容为
{ "watches": [{ "type": "services", "handler_type": "http", "http_handler_config": { "path": "https://localhost:5011/ConsulWatch/Refresh", "method": "GET", "header": { "x-foo": ["bar", "baz"] }, "timeout": "10s", "tls_skip_verify": true } }] }
启动 命令为 注意我这个是windows下的consul
consul agent - -config-dir=config -dev
新建一个consulwatchserveice服务项目 专门监控 consul节点的变化
主要代码如下
using AOPTest; using DotNetCore.CAP; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Threading.Tasks; namespace AOPConsul.Controllers { [Route("ConsulWatch")] [ApiController] public class ConsulWatchController : ControllerBase { private readonly ILogger<ConsulWatchController> _logger; private readonly ICapPublisher _capPublisher; public ConsulWatchController( ILogger<ConsulWatchController> logger, ICapPublisher capPublisher ) { _logger = logger; _capPublisher = capPublisher; } [HttpGet("Refresh")] public async Task<IActionResult> GetAsync() { await _capPublisher.PublishAsync(CapPublishName.CAPCONSULWATCH, ""); _logger.LogInformation("watch节点监控 发送刷新命令"); return Ok("刷新consul节点"); } } }
其余的项目 接收cap消息
主要代码如下
using AOPTest; using AOPTest.Consul; using DotNetCore.CAP; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace AOPWebApp.Controllers { [Route("Watch")] [ApiController] public class WatchController : ControllerBase { private readonly ILogger<WatchController> _logger; private readonly ICapPublisher _capPublisher; private readonly ICustomConsul _customConsul; public WatchController(ILogger<WatchController> logger, ICapPublisher capPublisher, ICustomConsul customConsul ) { _logger = logger; _capPublisher = capPublisher; _customConsul = customConsul; } /// <summary> /// consul 刷新节点 /// </summary> /// <returns></returns> [NonAction] [CapSubscribe(CapPublishName.CAPCONSULWATCH)] public IActionResult Refresh() { _customConsul.Refresh(); _logger.LogInformation("consul 节点刷新成功"); return Ok(); } } }
consul 封装代码
namespace AOPTest.Consul { public class ConsulServeiceNode { public string Url { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AOPTest.Consul { public interface ICustomConsul { /// <summary> /// 发现节点 /// </summary> /// <param name="serverName">服务名称</param> /// <returns></returns> List<ConsulServeiceNode> Discovery(string serverName); /// <summary> /// 发现所有节点 /// </summary> /// <returns></returns> List<ConsulServeiceNode> Discovery(); /// <summary> /// 注销 /// </summary> void Deregister(); /// <summary> /// 注册 /// </summary> Task RegistryAsync(); /// <summary> /// 刷新 /// </summary> void Refresh(); } }
using AOPTest.Consul.Options; using Consul; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace AOPTest.Consul { public class CustomConsul : ICustomConsul { public static Dictionary<string, List<ConsulServeiceNode>> dicServiceNodes = new Dictionary<string, List<ConsulServeiceNode>>(); private readonly ConsulOptions _options; public CustomConsul(IOptionsMonitor<ConsulOptions> options) { _options = options.CurrentValue; } /// <summary> /// 注销 /// </summary> public async void Deregister() { using ConsulClient client = GetConsulClient(); await client.Agent.ServiceDeregister(_options.ServiceId.ToString()); } /// <summary> /// 发现 节点 /// </summary> /// <param name="serverName">服务名称</param> /// <returns></returns> public List<ConsulServeiceNode> Discovery(string serverName) { using ConsulClient client = GetConsulClient(); List<ConsulServeiceNode> consulServeiceNodes = new List<ConsulServeiceNode>(); var dicServices = client.Catalog.Services().Result.Response; foreach (var dicService in dicServices) { string key = dicService.Key; //根据服务key获取相关的 生成相关的服务的节点 List<CatalogService> catalogServices = client.Catalog.Service(serverName).Result.Response.ToList(); consulServeiceNodes = catalogServices.Select(item => new ConsulServeiceNode { Url = $"{item.ServiceAddress}:{item.ServicePort}" }).ToList(); } return consulServeiceNodes; } /// <summary> /// 发现所有节点 /// </summary> /// <returns></returns> public List<ConsulServeiceNode> Discovery() { using ConsulClient client = GetConsulClient(); List<ConsulServeiceNode> consulServeiceNodes = new List<ConsulServeiceNode>(); var dicServices = client.Catalog.Services().Result.Response; foreach (var dicService in dicServices) { string key = dicService.Key; //根据服务key获取相关的 生成相关的服务的节点 List<CatalogService> catalogServices = client.Catalog.Service(key).Result.Response.ToList(); consulServeiceNodes = catalogServices.Select(item => new ConsulServeiceNode { Url = $"{item.ServiceAddress}:{item.ServicePort}" }).ToList(); } return consulServeiceNodes; } /// <summary> /// 刷新 /// </summary> public void Refresh() { //清空 字典 dicServiceNodes.Clear(); using ConsulClient client = GetConsulClient(); var dicServices = client.Catalog.Services().Result.Response; foreach (var dicService in dicServices) { string key = dicService.Key; //根据服务key获取相关的 生成相关的服务的节点 List<CatalogService> catalogServices = client.Catalog.Service(key).Result.Response.ToList(); List<ConsulServeiceNode> consulServeiceNodes = catalogServices.Select(item => new ConsulServeiceNode { Url = $"{item.ServiceAddress}:{item.ServicePort}" }).ToList(); //将节点存到字典中 dicServiceNodes.Add(key, consulServeiceNodes); } } /// <summary> /// 注册 /// </summary> public async Task RegistryAsync() { using ConsulClient client = GetConsulClient(); await client.Agent.ServiceRegister(new AgentServiceRegistration { ID = _options.ServiceId.ToString() ?? Guid.NewGuid().ToString(), Name = _options.ConsulRegister.Name, Address = _options.ConsulRegister.Address, Port = _options.ConsulRegister.Port, Tags = new string[] { _options.ConsulRegister.Tag }, Check = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(_options.ConsulRegister.DeregisterCriticalServiceAfter),//服务启动多久后注册 Interval = TimeSpan.FromSeconds(_options.ConsulRegister.Interval),//健康检查时间间隔 HTTP = _options.ConsulRegister.HealthCheckUrl,///健康检查地址 Timeout = TimeSpan.FromSeconds(_options.ConsulRegister.Timeout) } }); } /// <summary> /// 获取 ConsulClient /// </summary> /// <returns></returns> private ConsulClient GetConsulClient() { return new ConsulClient(config => { config.Address = new Uri(_options.ConsulMain.Address); config.Datacenter = _options.ConsulMain.Datacenter; }); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using AOPTest.Consul.Options; using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace AOPTest.Consul { public static class ConsulExtend { public static IServiceCollection AddCustomConsul(this IServiceCollection services, Action<ConsulOptions> action) { ConsulOptions consulOptions = new ConsulOptions(); services.Configure<ConsulOptions>(action); return services.AddTransient<ICustomConsul, CustomConsul>(); } public static IServiceCollection AddDefaultCustomConsul(this IServiceCollection services,IConfiguration configuration) { services.Configure<ConsulOptions>(configuration.GetSection("ConsulOptions")); return services.AddTransient<ICustomConsul, CustomConsul>(); } } }
using Consul; using Microsoft.Extensions.Hosting; using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using AOPTest.Consul.Options; namespace AOPTest.Consul { public class ConsulHostServices : IHostedService { private readonly ConsulOptions _options; private readonly ICustomConsul _customConsul; public ConsulHostServices(IOptionsMonitor<ConsulOptions> options, ICustomConsul customConsul) { _options = options.CurrentValue; _customConsul = customConsul; } public async Task StartAsync(CancellationToken cancellationToken) { //注册 await _customConsul.RegistryAsync(); } public Task StopAsync(CancellationToken cancellationToken) { //注销 _customConsul.Deregister(); return Task.CompletedTask; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AOPTest.Consul.Options { public class ConsulOptions { public ConsulMain ConsulMain { get; set; } public ConsulRegister ConsulRegister { get; set; } public Guid ServiceId { get; set; } public ConsulOptions() { ServiceId = Guid.NewGuid(); } } /// <summary> /// consul ui /// </summary> public class ConsulMain { public string Address { get; set; } public string Datacenter { get; set; } } /// <summary> /// consul 下节点 注册 /// </summary> public class ConsulRegister { public string Name { get; set; } public string Address { get; set; } public int Port { get; set; } public string Tag { get; set; } public string HealthCheckUrl { get; set; } public int Interval { get; set; } public int Timeout { get; set; } public int DeregisterCriticalServiceAfter { get; set; } } }
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "con": "server=192.168.0.192;port=3306;database=Cap1;uid=root;pwd=123456;" }, "ConsulOptions": { "ConsulMain": { "Address": "http://localhost:8500", "Datacenter": "dc1" }, "ConsulRegister": { "Name": "OrderAPI", "Address": "localhost", "Port": 5006, "Tag": "1", "HealthCheckUrl": "https://localhost:5006/HealthCheck", "Interval": 10, "Timeout": 5, "DeregisterCriticalServiceAfter": 20 } }, "CapOptions": { "FailedRetryCount": 3, "FailedRetryInterval": 60 }, "CapRabbitMqOptions": { "HostName": "192.168.0.192", "Port": 5672, "VirtualHost": "TestCap", "UserName": "admin", "Password": "admin" } }
consul 注册 在程序启动的时候 就开始注册 用的ihostservice
//添加 consul services.AddDefaultCustomConsul(Configuration); //程序开始就注册consul services.AddHostedService<ConsulHostServices>(); //添加cap services.AddCustomCap(Configuration);
//consul健康检查 app.Map("/HealthCheck", config => { config.Run(context => { context.Response.StatusCode = 200; logger.LogError($"{context.Request.Host}consul健康检查成功"); return Task.CompletedTask; }); });
.net core 使用 consul 包 如果 节点有变化 如何 让应用程序 感知 在 .NET Core 中使用 Consul 进行服务发现时,你可以通过注册一个 Consul 客户端,并使用该客户端来监控节点变化。Consul 提供了一个 健康检查 和 事件 系统,可以帮助你的应用程序感知节点的变化。 使用 Consul 监控节点变化的步骤: 安装 Consul NuGet 包: 确保你在项目中安装了 Consul NuGet 包。可以使用以下命令安装: dotnet add package Consul 配置 Consul 客户端: 在你的应用程序中创建一个 Consul 客户端实例。通常在 Startup.cs 中进行配置。 using Consul; public class Startup { private readonly ConsulClient _consulClient; public Startup() { _consulClient = new ConsulClient(config => { config.Address = new Uri("http://localhost:8500"); // Consul 服务器地址 }); } public void ConfigureServices(IServiceCollection services) { // 配置你的服务 services.AddSingleton(_consulClient); // 其他服务配置 } } 监控服务变更: 你可以使用 Catalog API 来监控服务的变化。你可以定期检查服务的健康状态,或者使用 Blocking 查询来保持与 Consul 的连接并监听变化。 public async Task MonitorServices() { var services = await _consulClient.Catalog.Services(); // 记录初始服务 var initialServices = services.Response; while (true) { var queryOptions = new QueryOptions { WaitTime = TimeSpan.FromSeconds(10) }; // 设置等待时间 var response = await _consulClient.Catalog.Services(queryOptions); var currentServices = response.Response; // 检查服务变化 if (!initialServices.SequenceEqual(currentServices)) { // 处理服务变化逻辑 Console.WriteLine("服务列表发生变化!"); initialServices = currentServices; // 更新初始服务列表 } } } 处理服务变化: 在服务变化的逻辑中,你可以实现应用程序需要采取的具体操作,比如重新加载配置、更新路由、通知用户等。 健康检查: 如果你希望 Consul 根据服务的健康状况来监控节点,可以在服务注册时添加健康检查配置。例如: var registration = new AgentServiceRegistration() { ID = "my-service-id", Service = "my-service", Address = "localhost", Port = 5000, Check = new AgentServiceCheck { HTTP = "http://localhost:5000/health", Interval = TimeSpan.FromSeconds(10), Timeout = TimeSpan.FromSeconds(5) } }; await _consulClient.Agent.ServiceRegister(registration); 总结 通过以上步骤,你可以在 .NET Core 应用程序中使用 Consul 客户端监控服务节点的变化。利用 Consul 的健康检查和阻塞查询特性,可以实现应用程序对服务变更的实时感知,进而采取相应的业务逻辑处理。确保在生产环境中合理设置阻塞查询的超时时间,以避免过度消耗资源。