ASP.NET Core中延迟后台服务初始化以避免启动崩溃
问题:ASP.NET Core后台服务初始化导致API启动崩溃,无法使用健康检查确认连接状态
我有一个ASP.NET Core Web API,启动时会启动后台服务RabbitLogConsumer,该服务负责连接RabbitMQ队列、读取传入消息并将其存入MongoDB。当前代码中,后台服务的构造函数会直接初始化RabbitMQ连接,一旦RabbitMQ无法连接,API会直接启动失败,导致我无法通过已实现的ConnectionHealthCheck健康检查先确认连接状态。我希望延迟这个后台服务的初始化操作。
原RabbitLogConsumer代码:
public class RabbitLogConsumer : BackgroundService { private readonly RabbitMQ.Client.IConnection _connection; private readonly IModel _channel; private readonly ILogger _logger; private readonly ChannelType _channelType; private readonly ConsumerOptions _consumerOptions; private readonly RabbitMqOptions _rabbitMqOptions; private readonly LogService _logService; public RabbitLogConsumer(IOptions<RabbitMqOptions> options, IOptions<ConsumerOptions> consumerOptions, ILogger<RabbitLogConsumer> logger ,LogService logService) { _logger = logger; _consumerOptions = consumerOptions.Value; var factory = new ConnectionFactory { Uri = new Uri(options.Value.ConnectionString)}; _connection = factory.CreateConnection(); _channel = _connection.CreateModel(); _channel.BasicQos(_consumerOptions.PrefetchSize, _consumerOptions.PrefetchCount, false); _logService = logService; _rabbitMqOptions = options.Value; _channelType = ChannelType.Queue; DeclareEntity(); } protected override Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Started Background Task!"); stoppingToken.ThrowIfCancellationRequested(); var consumer = new EventingBasicConsumer(_channel); consumer.Received += async (ch, ea) => { try { _logger.LogInformation("Received Message"); var content = Encoding.UTF8.GetString(ea.Body.ToArray()); var logRequest = JsonSerializer.Deserialize<LogRequest>(content) ?? throw new Exception("LogRequest was null"); var serviceLog = new ServiceLog { ServiceMessage = logRequest.ServiceMessage, ServiceName = logRequest.ServiceName, CreatedAt = logRequest.Time.ToDateTimeOffset() }; await _logService.AddLog(serviceLog); _channel.BasicAck(ea.DeliveryTag, multiple: false); } catch (Exception ex) { _logger.LogError("Couldn't save log\n {error}", ex); _channel.BasicNack(ea.DeliveryTag, false,false); } }; _channel.BasicConsume(_consumerOptions.ChannelTarget, false, consumer); return Task.CompletedTask; } private void DeclareEntity() { if (_channelType == ChannelType.Queue) { DeclareQueue(); } else if (_channelType == ChannelType.Exchange) { DeclareExchange(); } } private void DeclareQueue() { _channel.QueueDeclarePassive(_consumerOptions.ChannelTarget); } private void DeclareExchange() { _channel.ExchangeDeclarePassive(_consumerOptions.ChannelTarget); } }
已实现的ConnectionHealthCheck代码:
public class ConnectionHealthCheck : IHealthCheck { private readonly ILogger _logger; private readonly IOptions<RabbitMqOptions> _options; private readonly IOptions<DatabaseSettings> _databaseSettings; public ConnectionHealthCheck(IOptions<RabbitMqOptions> options, ILogger<ConnectionHealthCheck> logger) { _options = options; _logger = logger; } public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var factory = new ConnectionFactory { Uri = new Uri(_options.Value.ConnectionString) }; using var connection = factory.CreateConnection(); using var channel = connection.CreateModel(); channel.QueueDeclarePassive("Notifications"); if (connection.IsOpen) { return Task.FromResult(HealthCheckResult.Healthy("Rabbit connection is working")); } throw new Exception("Rabbit Connection Health Check Failed"); } catch (Exception) { return Task.FromResult(HealthCheckResult.Unhealthy("Rabbit Connection Health Check Failed")); } } }
最简洁的处理方式:延迟初始化+连接重试
核心思路是把所有可能失败的RabbitMQ初始化逻辑从构造函数移到ExecuteAsync方法,让API能正常启动,同时添加重试逻辑确保后台服务能在RabbitMQ恢复后自动连接。
修改后的RabbitLogConsumer代码:
public class RabbitLogConsumer : BackgroundService, IDisposable { private readonly ILogger<RabbitLogConsumer> _logger; private readonly ConsumerOptions _consumerOptions; private readonly RabbitMqOptions _rabbitMqOptions; private readonly LogService _logService; private readonly ChannelType _channelType = ChannelType.Queue; // 延迟初始化的连接和通道 private IConnection _connection; private IModel _channel; private readonly ConnectionFactory _connectionFactory; public RabbitLogConsumer(IOptions<RabbitMqOptions> options, IOptions<ConsumerOptions> consumerOptions, ILogger<RabbitLogConsumer> logger, LogService logService) { _logger = logger; _consumerOptions = consumerOptions.Value; _logService = logService; _rabbitMqOptions = options.Value; // 只初始化工厂,不创建连接 _connectionFactory = new ConnectionFactory { Uri = new Uri(options.Value.ConnectionString) }; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Background service started, waiting for RabbitMQ connection..."); // 循环尝试连接,直到服务停止或连接成功 while (!stoppingToken.IsCancellationRequested) { try { // 初始化连接和通道 _connection = _connectionFactory.CreateConnection(); _channel = _connection.CreateModel(); _channel.BasicQos(_consumerOptions.PrefetchSize, _consumerOptions.PrefetchCount, false); DeclareEntity(); _logger.LogInformation("Successfully connected to RabbitMQ"); await StartConsuming(stoppingToken); break; // 连接成功后退出循环 } catch (Exception ex) { _logger.LogError(ex, "Failed to connect to RabbitMQ, retrying in 5 seconds..."); // 等待5秒后重试 await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } } } private async Task StartConsuming(CancellationToken stoppingToken) { var consumer = new EventingBasicConsumer(_channel); consumer.Received += async (ch, ea) => { try { _logger.LogInformation("Received Message"); var content = Encoding.UTF8.GetString(ea.Body.ToArray()); var logRequest = JsonSerializer.Deserialize<LogRequest>(content) ?? throw new Exception("LogRequest was null"); var serviceLog = new ServiceLog { ServiceMessage = logRequest.ServiceMessage, ServiceName = logRequest.ServiceName, CreatedAt = logRequest.Time.ToDateTimeOffset() }; await _logService.AddLog(serviceLog); _channel.BasicAck(ea.DeliveryTag, multiple: false); } catch (Exception ex) { _logger.LogError("Couldn't save log\n {error}", ex); _channel.BasicNack(ea.DeliveryTag, false, false); } }; // 监听连接断开事件,触发重新连接 _connection.ConnectionShutdown += async (s, e) => { _logger.LogWarning("RabbitMQ connection lost, attempting to reconnect..."); await ExecuteAsync(stoppingToken); }; _channel.BasicConsume(_consumerOptions.ChannelTarget, false, consumer); // 等待服务停止信号 await Task.Delay(Timeout.Infinite, stoppingToken); } private void DeclareEntity() { if (_channelType == ChannelType.Queue) { _channel.QueueDeclarePassive(_consumerOptions.ChannelTarget); } else if (_channelType == ChannelType.Exchange) { _channel.ExchangeDeclarePassive(_consumerOptions.ChannelTarget); } } // 实现IDisposable释放资源 public override void Dispose() { _channel?.Close(); _connection?.Close(); base.Dispose(); } }
关键修改点说明:
- 构造函数只做轻量初始化:仅注入依赖和创建
ConnectionFactory,不执行任何可能失败的连接操作,确保API能正常启动,健康检查可以正常运行。 ExecuteAsync中添加连接重试:循环尝试连接RabbitMQ,失败后等待一段时间重试,直到连接成功或服务停止。- 监听连接断开事件:当RabbitMQ连接意外断开时,自动触发重新连接逻辑,提升服务健壮性。
- 实现
IDisposable:确保服务停止时正确关闭RabbitMQ连接和通道,避免资源泄漏。
这样修改后,API启动时不会因RabbitMQ连接失败而崩溃,你可以通过健康检查接口确认RabbitMQ和MongoDB的状态,后台服务会在连接可用时自动开始消费消息。
内容的提问来源于stack exchange,提问作者IOEnthusiast
相关产品推荐
相关产品推荐

