You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 04:44:57