From 3fc3bb8144722ddb70966f6001a169e01de01be0 Mon Sep 17 00:00:00 2001 From: "seonkyu.kim" Date: Tue, 10 Feb 2026 19:15:42 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20RabbitMQ=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=88=ED=84=B0=EB=A7=81=20=EB=B0=8F=20=EB=B0=B1?= =?UTF-8?q?=EA=B7=B8=EB=9D=BC=EC=9A=B4=EB=93=9C=20=EC=9E=AC=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=20=EC=B6=94=EA=B0=80=20(#124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RabbitMQInitializer를 BackgroundService로 변경 (30초 간격 재시도) - RabbitMQConnection에 IsConnected 속성 추가 - Health check에 RabbitMQ 연결/초기화 상태 반영 - DI 등록 변경 (Singleton + HostedService 패턴) Co-Authored-By: Claude Opus 4.6 --- SPMS.API/Controllers/PublicController.cs | 31 ++++++++++++++++--- SPMS.Infrastructure/DependencyInjection.cs | 3 +- .../Messaging/RabbitMQConnection.cs | 2 ++ .../Messaging/RabbitMQInitializer.cs | 30 +++++++++++------- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/SPMS.API/Controllers/PublicController.cs b/SPMS.API/Controllers/PublicController.cs index 47ffc71..f6d16e3 100644 --- a/SPMS.API/Controllers/PublicController.cs +++ b/SPMS.API/Controllers/PublicController.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Swashbuckle.AspNetCore.Annotations; using SPMS.Domain.Common; using SPMS.Infrastructure; +using SPMS.Infrastructure.Messaging; namespace SPMS.API.Controllers; @@ -14,10 +15,17 @@ namespace SPMS.API.Controllers; public class PublicController : ControllerBase { private readonly AppDbContext _dbContext; + private readonly RabbitMQConnection _rabbitConnection; + private readonly RabbitMQInitializer _rabbitInitializer; - public PublicController(AppDbContext dbContext) + public PublicController( + AppDbContext dbContext, + RabbitMQConnection rabbitConnection, + RabbitMQInitializer rabbitInitializer) { _dbContext = dbContext; + _rabbitConnection = rabbitConnection; + _rabbitInitializer = rabbitInitializer; } [HttpPost("health")] @@ -39,11 +47,26 @@ public class PublicController : ControllerBase allHealthy = false; } - // 2. Redis 연결 확인 (Phase 2에서 구현 예정) + // 2. Redis 연결 확인 (Phase 3-2에서 구현 예정) checks["redis"] = new { status = "not_configured" }; - // 3. RabbitMQ 연결 확인 (Phase 2에서 구현 예정) - checks["rabbitmq"] = new { status = "not_configured" }; + // 3. RabbitMQ 연결 확인 + var rabbitConnected = _rabbitConnection.IsConnected; + var rabbitInitialized = _rabbitInitializer.IsInitialized; + if (rabbitConnected && rabbitInitialized) + { + checks["rabbitmq"] = new { status = "healthy" }; + } + else + { + checks["rabbitmq"] = new + { + status = "unhealthy", + connected = rabbitConnected, + initialized = rabbitInitialized + }; + allHealthy = false; + } if (allHealthy) { diff --git a/SPMS.Infrastructure/DependencyInjection.cs b/SPMS.Infrastructure/DependencyInjection.cs index cadd401..87d794f 100644 --- a/SPMS.Infrastructure/DependencyInjection.cs +++ b/SPMS.Infrastructure/DependencyInjection.cs @@ -58,7 +58,8 @@ public static class DependencyInjection // RabbitMQ services.Configure(configuration.GetSection(RabbitMQSettings.SectionName)); services.AddSingleton(); - services.AddHostedService(); + services.AddSingleton(); + services.AddHostedService(sp => sp.GetRequiredService()); services.AddScoped(); // Push Senders diff --git a/SPMS.Infrastructure/Messaging/RabbitMQConnection.cs b/SPMS.Infrastructure/Messaging/RabbitMQConnection.cs index bb80f6e..94d4c22 100644 --- a/SPMS.Infrastructure/Messaging/RabbitMQConnection.cs +++ b/SPMS.Infrastructure/Messaging/RabbitMQConnection.cs @@ -12,6 +12,8 @@ public class RabbitMQConnection : IAsyncDisposable private readonly SemaphoreSlim _semaphore = new(1, 1); private IConnection? _connection; + public bool IsConnected => _connection is { IsOpen: true }; + public RabbitMQConnection( IOptions settings, ILogger logger) diff --git a/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs b/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs index 7926e2f..0016765 100644 --- a/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs +++ b/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs @@ -6,11 +6,14 @@ using SPMS.Application.Settings; namespace SPMS.Infrastructure.Messaging; -public class RabbitMQInitializer : IHostedService +public class RabbitMQInitializer : BackgroundService { private readonly RabbitMQConnection _connection; private readonly RabbitMQSettings _settings; private readonly ILogger _logger; + private static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(30); + + public bool IsInitialized { get; private set; } public RabbitMQInitializer( RabbitMQConnection connection, @@ -22,19 +25,26 @@ public class RabbitMQInitializer : IHostedService _logger = logger; } - public async Task StartAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - try + while (!stoppingToken.IsCancellationRequested) { - await InitializeAsync(cancellationToken); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "RabbitMQ 초기화 실패 — 서버는 계속 실행됩니다. 메시지 발송 시 재연결을 시도합니다."); + try + { + await InitializeAsync(stoppingToken); + IsInitialized = true; + _logger.LogInformation("RabbitMQ 초기화 완료"); + return; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "RabbitMQ 초기화 실패 — {RetrySeconds}초 후 재시도합니다.", RetryInterval.TotalSeconds); + await Task.Delay(RetryInterval, stoppingToken); + } } } - public async Task InitializeAsync(CancellationToken cancellationToken) + private async Task InitializeAsync(CancellationToken cancellationToken) { await using var channel = await _connection.CreateChannelAsync(cancellationToken); @@ -87,6 +97,4 @@ public class RabbitMQInitializer : IHostedService _logger.LogInformation("Queue 선언 및 바인딩 완료: {Queue} → {Exchange} (routing_key: schedule)", _settings.ScheduleQueue, _settings.Exchange); } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }