NETCORE - Consul 注册 新
NETCORE - Consul 注册 新
一. Consul服务端部署
参考:Docker - 部署Consul 新 - 无心々菜 - 博客园
二. 在.net8中注入Consul服务
安装Nuget包


增加服务配置,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.

浙公网安备 33010602011771号