NETCORE - Consul 注册 新

 NETCORE - Consul 注册 新

 

一. Consul服务端部署

 参考:Docker - 部署Consul 新 - 无心々菜 - 博客园

 二. 在.net8中注入Consul服务

安装Nuget包

 image

image

 

 增加服务配置,appsettings.json

由于我使用的是docker发布服务,所以都使用的宿主机的IP地址。并没有使用localhost

  "Consul": {
    "ConsulPath": "http://10.10.0.98:8500",
    "ServiceName": "api-assist",
    "ServiceScheme": "https",
    "ServiceAddress": "10.10.0.98",
    "ServicePort": 7096,
    "Tags": [ "dotnet8", "consul" ],
    "HealthCheck": "/health",
    "HealthCheckInterval": 10,
    "HealthCheckTimeout": 5,
    "DeregisterCriticalServiceAfter": 30
  }

 

 

 增加 静态扩展类  ConsulServiceExtensions.cs

using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Rail.Medium.Middleware
{
    public class ConsulConfig
    {
        public string ConsulPath { get; set; } = "http://localhost:8500";
        public string ServiceName { get; set; } = "api-service";
        public string ServiceScheme { get; set; } = "http";
        public string ServiceAddress { get; set; } = "localhost";
        public int ServicePort { get; set; } = 5000;
        public string[] Tags { get; set; } = Array.Empty<string>();
        public string HealthCheck { get; set; } = "/health";
        public int HealthCheckInterval { get; set; } = 10;
        public int HealthCheckTimeout { get; set; } = 5;
        public int DeregisterCriticalServiceAfter { get; set; } = 30;

        // 修复:使用固定的 ServiceId,避免每次重新生成
        public string ServiceId
        {
            get
            {
                var hostName = Dns.GetHostName();
                return $"{ServiceName}-{hostName}-{ServicePort}";
            }
        }
    }

    public static class ConsulServiceExtensions
    {
        public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration)
        {
            // 先获取配置,确保配置正确
            var consulConfig = configuration.GetSection("Consul").Get<ConsulConfig>();
            if (consulConfig == null)
            {
                consulConfig = new ConsulConfig();
            }

            services.Configure<ConsulConfig>(configuration.GetSection("Consul"));

            services.AddSingleton<IConsulClient>(provider =>
            {
                var config = configuration.GetSection("Consul").Get<ConsulConfig>() ?? new ConsulConfig();
                return new ConsulClient(consulConfig =>
                {
                    consulConfig.Address = new Uri(config.ConsulPath);
                });
            });

            return services;
        }

        public static IApplicationBuilder UseConsul(this IApplicationBuilder app)
        {
            var consulClient = app.ApplicationServices.GetRequiredService<IConsulClient>();
            var logger = app.ApplicationServices.GetRequiredService<ILogger<IApplicationBuilder>>();
            var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
            var configuration = app.ApplicationServices.GetRequiredService<IConfiguration>();

            // 获取 Consul 配置
            var consulConfig = configuration.GetSection("Consul").Get<ConsulConfig>();
            if (consulConfig == null)
            {
                logger.LogWarning("Consul 配置未找到,使用默认配置");
                consulConfig = new ConsulConfig();
            }

            // 验证健康检查端点是否存在
            logger.LogInformation("健康检查地址: {HealthCheckUrl}",
                $"{consulConfig.ServiceScheme}://{consulConfig.ServiceAddress}:{consulConfig.ServicePort}{consulConfig.HealthCheck}");

            // 注册服务
            RegisterService(consulClient, consulConfig, logger, lifetime);

            return app;
        }

        private static void RegisterService(IConsulClient consulClient, ConsulConfig consulConfig, ILogger logger, IHostApplicationLifetime lifetime)
        {
            try
            {
                var registration = new AgentServiceRegistration()
                {
                    ID = consulConfig.ServiceId,
                    Name = consulConfig.ServiceName,
                    Address = consulConfig.ServiceAddress,
                    Port = consulConfig.ServicePort,
                    Tags = consulConfig.Tags,
                    Check = new AgentServiceCheck()
                    {
                        HTTP = $"{consulConfig.ServiceScheme}://{consulConfig.ServiceAddress}:{consulConfig.ServicePort}{consulConfig.HealthCheck}",
                        Interval = TimeSpan.FromSeconds(consulConfig.HealthCheckInterval),
                        Timeout = TimeSpan.FromSeconds(consulConfig.HealthCheckTimeout),
                        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(consulConfig.DeregisterCriticalServiceAfter),
                        Status = HealthStatus.Passing,
                        TLSSkipVerify = consulConfig.ServiceScheme == "https" // 自签名证书跳过验证
                    }
                };

                logger.LogInformation("正在向 Consul 注册服务: {ServiceName} (ID: {ServiceId})",
                    consulConfig.ServiceName, consulConfig.ServiceId);
                logger.LogInformation("服务地址: {ServiceAddress}:{ServicePort}",
                    consulConfig.ServiceAddress, consulConfig.ServicePort);
                logger.LogInformation("健康检查: {HealthCheck}", registration.Check.HTTP);

                // 异步注册
                consulClient.Agent.ServiceRegister(registration).GetAwaiter().GetResult();

                logger.LogInformation("服务注册成功");

                // 应用停止时注销服务
                lifetime.ApplicationStopping.Register(() =>
                {
                    try
                    {
                        logger.LogInformation("正在从 Consul 注销服务: {ServiceId}", consulConfig.ServiceId);
                        consulClient.Agent.ServiceDeregister(consulConfig.ServiceId).GetAwaiter().GetResult();
                        logger.LogInformation("服务注销成功");
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "服务注销失败");
                    }
                });
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "服务注册失败");
                throw;
            }
        }
    }
}

 

 

 Program.cs 里面注入

builder.Services.AddConsul(builder.Configuration);

 

app.UseConsul();

 

 

 三. 注意需要提前 集成好 健康检查 功能

健康检查:NETCORE - 健康检查health - 无心々菜 - 博客园

 

 

 四. 注入优化

  当Consul 注册失败时,不影响主项目启动

修改后的 ConsulServiceExtensions.cs 文件

using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Rail.Medium.Middleware
{
    public class ConsulConfig
    {
        public string ConsulPath { get; set; } = "http://localhost:8500";
        public string ServiceName { get; set; } = "api-service";
        public string ServiceScheme { get; set; } = "http";
        public string ServiceAddress { get; set; } = "localhost";
        public int ServicePort { get; set; } = 5000;
        public string[] Tags { get; set; } = Array.Empty<string>();
        public string HealthCheck { get; set; } = "/health";
        public int HealthCheckInterval { get; set; } = 10;
        public int HealthCheckTimeout { get; set; } = 5;
        public int DeregisterCriticalServiceAfter { get; set; } = 30;

        // 修复:使用固定的 ServiceId,避免每次重新生成 
        public string ServiceId => $"{ServiceName}-{Dns.GetHostName()}-{ServicePort}";

    }


    public static class ConsulServiceExtensions
    {
        public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration)
        {
            // 先获取配置,确保配置正确
            var consulConfig = configuration.GetSection("Consul").Get<ConsulConfig>() ?? new ConsulConfig();

            services.Configure<ConsulConfig>(configuration.GetSection("Consul"));

            services.AddSingleton<IConsulClient>(provider =>
            {
                try
                {
                    return new ConsulClient(cfg =>
                    {
                        cfg.Address = new Uri(consulConfig.ConsulPath);
                    });
                }
                catch (Exception ex)
                {
                    var logger = provider.GetRequiredService<ILogger<IConsulClient>>();
                    logger.LogWarning(ex, "初始化 Consul 客户端失败,将跳过服务注册。");
                    return new ConsulClient(cfg => { }); // 返回空对象,防止空引用
                }

            });

            return services;
        }

        public static IApplicationBuilder UseConsul(this IApplicationBuilder app)
        {
            var consulClient = app.ApplicationServices.GetRequiredService<IConsulClient>();
            var logger = app.ApplicationServices.GetRequiredService<ILogger<IApplicationBuilder>>();
            var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
            var configuration = app.ApplicationServices.GetRequiredService<IConfiguration>();

            var consulConfig = configuration.GetSection("Consul").Get<ConsulConfig>() ?? new ConsulConfig();

            var healthCheckUrl = $"{consulConfig.ServiceScheme}://{consulConfig.ServiceAddress}:{consulConfig.ServicePort}{consulConfig.HealthCheck}";

            logger.LogInformation("准备注册 Consul 服务: {ServiceName}, 健康检查地址: {HealthCheckUrl}",
             consulConfig.ServiceName, healthCheckUrl);

            // 注册服务(即使失败也不抛出)
            Task.Run(() =>
            {
                try
                {
                    RegisterService(consulClient, consulConfig, logger, lifetime);
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, "Consul 注册失败,应用将继续启动。");
                }
            });
            return app;
        }

        private static void RegisterService(IConsulClient consulClient, ConsulConfig consulConfig, ILogger logger, IHostApplicationLifetime lifetime)
        {
            var registration = new AgentServiceRegistration()
            {
                ID = consulConfig.ServiceId,
                Name = consulConfig.ServiceName,
                Address = consulConfig.ServiceAddress,
                Port = consulConfig.ServicePort,
                Tags = consulConfig.Tags,
                Check = new AgentServiceCheck()
                {
                    HTTP = $"{consulConfig.ServiceScheme}://{consulConfig.ServiceAddress}:{consulConfig.ServicePort}{consulConfig.HealthCheck}",
                    Interval = TimeSpan.FromSeconds(consulConfig.HealthCheckInterval),
                    Timeout = TimeSpan.FromSeconds(consulConfig.HealthCheckTimeout),
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(consulConfig.DeregisterCriticalServiceAfter),
                    Status = HealthStatus.Passing,
                    TLSSkipVerify = consulConfig.ServiceScheme == "https" // 自签名证书跳过验证
                }
            };

            try
            {
                consulClient.Agent.ServiceRegister(registration).GetAwaiter().GetResult();
                logger.LogInformation("Consul 注册成功: {ServiceName} ({ServiceId})", consulConfig.ServiceName, consulConfig.ServiceId);
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Consul 注册失败,服务仍将继续运行。");
                return;
            }

            // 应用停止时注销服务
            lifetime.ApplicationStopping.Register(() =>
            {
                try
                {
                    logger.LogInformation("正在从 Consul 注销服务: {ServiceId}", consulConfig.ServiceId);
                    consulClient.Agent.ServiceDeregister(consulConfig.ServiceId).GetAwaiter().GetResult();
                    logger.LogInformation("服务注销成功");
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "服务注销失败");
                }
            });
        }
    }
}

 

 

 

四. Consul 注册失败自动重试机制

 比如服务刚启动时 Consul 还没 ready、或者网络瞬时波动,如果只注册一次就失败,那么整个服务在 Consul 中就不可见了。

ConsulServiceExtensions.cs

using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Rail.Medium.Middleware
{
    public class ConsulConfig
    {
        public string ConsulPath { get; set; } = "http://localhost:8500";
        public string ServiceName { get; set; } = "api-service";
        public string ServiceScheme { get; set; } = "http";
        public string ServiceAddress { get; set; } = "localhost";
        public int ServicePort { get; set; } = 5000;
        public string[] Tags { get; set; } = Array.Empty<string>();
        public string HealthCheck { get; set; } = "/health";
        public int HealthCheckInterval { get; set; } = 10;
        public int HealthCheckTimeout { get; set; } = 5;
        public int DeregisterCriticalServiceAfter { get; set; } = 30;

        // 修复:使用固定的 ServiceId,避免每次重新生成 
        public string ServiceId => $"{ServiceName}-{Dns.GetHostName()}-{ServicePort}";
    }

    public static class ConsulServiceExtensions
    {
        public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration)
        {
            // 先获取配置,确保配置正确
            var consulConfig = configuration.GetSection("Consul").Get<ConsulConfig>() ?? new ConsulConfig();

            services.Configure<ConsulConfig>(configuration.GetSection("Consul"));

            services.AddSingleton<IConsulClient>(provider =>
            {
                try
                {
                    return new ConsulClient(cfg =>
                    {
                        cfg.Address = new Uri(consulConfig.ConsulPath);
                    });
                }
                catch (Exception ex)
                {
                    var logger = provider.GetRequiredService<ILogger<IConsulClient>>();
                    logger.LogWarning(ex, "初始化 Consul 客户端失败,将跳过服务注册。");
                    return new ConsulClient(cfg => { }); // 返回空对象,防止空引用
                }
            });
            return services;
        }
    }
}

 

 

增加 ConsulBackgroundService.cs

服务启动时自动注册 → 🔁 如果 Consul 挂了或注册失败 → 🕵️‍♂️ 后台定时检测 Consul 状态 → 💡 Consul 恢复后自动补注册

using Consul;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Rail.Medium.Middleware
{
    public class ConsulBackgroundService : BackgroundService
    {
        private readonly IConsulClient _consulClient;
        private readonly ConsulConfig _config;
        private readonly ILogger<ConsulBackgroundService> _logger;

        private bool _isRegistered = false;
         

        public ConsulBackgroundService(
            IConsulClient consulClient,
            IOptions<ConsulConfig> options,
            ILogger<ConsulBackgroundService> logger)
        {
            _consulClient = consulClient;
            _config = options.Value;
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("启动 Consul 后台注册服务...");

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    // 1️⃣ 检查 Consul 是否可用
                    if (await IsConsulAvailableAsync())
                    {
                        if (!_isRegistered)
                        {
                            // 2️⃣ 注册服务
                            await RegisterServiceAsync();
                            _isRegistered = true;
                        }
                    }
                    else
                    {
                        _logger.LogWarning("Consul 暂不可用,等待中...");
                        _isRegistered = false;
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Consul 检测/注册时发生异常。");
                    _isRegistered = false;
                }

                // 3️⃣ 等待下一次检测
                await Task.Delay(TimeSpan.FromSeconds(_config.HealthCheckInterval), stoppingToken);
            }
        }
        private async Task<bool> IsConsulAvailableAsync()
        {
            try
            {
                var status = await _consulClient.Status.Leader();
                return !string.IsNullOrEmpty(status);
            }
            catch
            {
                return false;
            }
        }

        private async Task RegisterServiceAsync()
        {
            var registration = new AgentServiceRegistration()
            {
                ID = _config.ServiceId,
                Name = _config.ServiceName,
                Address = _config.ServiceAddress,
                Port = _config.ServicePort,
                Tags = _config.Tags,
                Check = new AgentServiceCheck()
                {
                    HTTP = $"{_config.ServiceScheme}://{_config.ServiceAddress}:{_config.ServicePort}{_config.HealthCheck}",
                    Interval = TimeSpan.FromSeconds(_config.HealthCheckInterval),
                    Timeout = TimeSpan.FromSeconds(_config.HealthCheckTimeout),
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(_config.DeregisterCriticalServiceAfter),
                    TLSSkipVerify = _config.ServiceScheme == "https"
                }
            };

            try
            {
                await _consulClient.Agent.ServiceRegister(registration);
                _logger.LogInformation("✅ 已注册到 Consul: {ServiceId}", _config.ServiceId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "❌ Consul 注册失败,将在下次检测时重试。");
                _isRegistered = false;
            }
        }

        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Consul 后台服务停止中,开始注销服务...");
            try
            {
                await _consulClient.Agent.ServiceDeregister(_config.ServiceId);
                _logger.LogInformation("🟡 Consul 注销成功: {ServiceId}", _config.ServiceId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Consul 注销失败。");
            }
            await base.StopAsync(cancellationToken);
        }
    }
}

 

 

服务注册

builder.Services.AddConsul(builder.Configuration);
builder.Services.AddHostedService<ConsulBackgroundService>();

 

 

如果先部署了项目,后打开了consul,也会自动注册

 

 

支持多个consul节点注册

  "Consul": {
    "ClusterAddresses": [
      "http://10.10.0.11:8500",
      "http://10.10.0.12:8500",
      "http://10.10.0.13:8500"
    ],
    "ServiceName": "api-assist",
    "ServiceScheme": "http",
    "ServiceAddress": "10.10.0.167",
    "ServicePort": 7232,
    "Tags": [ "dotnet8", "consul" ],
    "HealthCheck": "/health",
    "HealthCheckInterval": 10,
    "HealthCheckTimeout": 5,
    "DeregisterCriticalServiceAfter": 30
  }

 

ConsulServiceExtensions.cs

using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Rail.Medium.Middleware
{
    public class ConsulConfig
    {
        public List<string> ClusterAddresses { get; set; } = new(); // 多节点支持
        public string ServiceName { get; set; } = "api-service";
        public string ServiceScheme { get; set; } = "http";
        public string ServiceAddress { get; set; } = "localhost";
        public int ServicePort { get; set; } = 5000;
        public string[] Tags { get; set; } = Array.Empty<string>();
        public string HealthCheck { get; set; } = "/health";
        public int HealthCheckInterval { get; set; } = 10;
        public int HealthCheckTimeout { get; set; } = 5;
        public int DeregisterCriticalServiceAfter { get; set; } = 30;

        // 修复:使用固定的 ServiceId,避免每次重新生成 
        public string ServiceId => $"{ServiceName}-{Dns.GetHostName()}-{ServicePort}";

    }



    public static class ConsulServiceExtensions
    {
        public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration)
        {
            // 1️⃣ 读取 Consul 配置
            var consulConfig = configuration.GetSection("Consul").Get<ConsulConfig>() ?? new ConsulConfig();
            services.Configure<ConsulConfig>(configuration.GetSection("Consul"));

            services.AddSingleton<IConsulClient>(provider =>
            {
                var logger = provider.GetRequiredService<ILogger<IConsulClient>>();

                // 支持多个节点
                var nodes = consulConfig.ClusterAddresses;

                foreach (var addr in nodes)
                {
                    try
                    {
                        var client = new ConsulClient(cfg => cfg.Address = new Uri(addr));

                        // 测试连接可用性
                        var status = client.Status.Leader().Result;
                        if (!string.IsNullOrEmpty(status))
                        {
                            logger.LogInformation("✅ 使用 Consul 节点: {Address}", addr);
                            return client;
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogWarning(ex, "⚠️ Consul 节点不可用: {Address}", addr);
                    }
                }

                // 所有节点都不可用时,返回一个“空客户端”,避免空引用异常
                logger.LogError("❌ 所有 Consul 节点均不可用!");
                return new ConsulClient(cfg => { });
            });

            return services;
        }
    }
}

 

 

 

 

 

 

 

end.

 

posted @ 2024-11-29 15:50  无心々菜  阅读(16)  评论(0)    收藏  举报