From efd66158097f84cc5a89c39b1d963dc54f13b357 Mon Sep 17 00:00:00 2001 From: "seonkyu.kim" Date: Tue, 10 Feb 2026 19:05:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20RabbitMQ=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=20=EC=95=B1=20=ED=81=AC=EB=9E=98?= =?UTF-8?q?=EC=8B=9C=20=EB=B0=A9=EC=A7=80=20(#124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StartAsync에서 throw 제거, LogWarning으로 변경 - InitializeAsync 메서드 분리 (재시도 가능) Co-Authored-By: Claude Opus 4.6 --- .../Messaging/RabbitMQInitializer.cs | 111 +++++++++--------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs b/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs index 1c22bba..7926e2f 100644 --- a/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs +++ b/SPMS.Infrastructure/Messaging/RabbitMQInitializer.cs @@ -26,66 +26,67 @@ public class RabbitMQInitializer : IHostedService { try { - await using var channel = await _connection.CreateChannelAsync(cancellationToken); - - // Exchange 선언: Direct, Durable - await channel.ExchangeDeclareAsync( - exchange: _settings.Exchange, - type: ExchangeType.Direct, - durable: true, - autoDelete: false, - arguments: null, - cancellationToken: cancellationToken); - - _logger.LogInformation("Exchange 선언 완료: {Exchange}", _settings.Exchange); - - var queueArgs = new Dictionary - { - { "x-message-ttl", _settings.MessageTtl } - }; - - // Push Queue 선언 - await channel.QueueDeclareAsync( - queue: _settings.PushQueue, - durable: true, - exclusive: false, - autoDelete: false, - arguments: queueArgs, - cancellationToken: cancellationToken); - - await channel.QueueBindAsync( - queue: _settings.PushQueue, - exchange: _settings.Exchange, - routingKey: "push", - cancellationToken: cancellationToken); - - _logger.LogInformation("Queue 선언 및 바인딩 완료: {Queue} → {Exchange} (routing_key: push)", - _settings.PushQueue, _settings.Exchange); - - // Schedule Queue 선언 - await channel.QueueDeclareAsync( - queue: _settings.ScheduleQueue, - durable: true, - exclusive: false, - autoDelete: false, - arguments: queueArgs, - cancellationToken: cancellationToken); - - await channel.QueueBindAsync( - queue: _settings.ScheduleQueue, - exchange: _settings.Exchange, - routingKey: "schedule", - cancellationToken: cancellationToken); - - _logger.LogInformation("Queue 선언 및 바인딩 완료: {Queue} → {Exchange} (routing_key: schedule)", - _settings.ScheduleQueue, _settings.Exchange); + await InitializeAsync(cancellationToken); } catch (Exception ex) { - _logger.LogError(ex, "RabbitMQ Exchange/Queue 초기화 실패"); - throw; + _logger.LogWarning(ex, "RabbitMQ 초기화 실패 — 서버는 계속 실행됩니다. 메시지 발송 시 재연결을 시도합니다."); } } + public async Task InitializeAsync(CancellationToken cancellationToken) + { + await using var channel = await _connection.CreateChannelAsync(cancellationToken); + + await channel.ExchangeDeclareAsync( + exchange: _settings.Exchange, + type: ExchangeType.Direct, + durable: true, + autoDelete: false, + arguments: null, + cancellationToken: cancellationToken); + + _logger.LogInformation("Exchange 선언 완료: {Exchange}", _settings.Exchange); + + var queueArgs = new Dictionary + { + { "x-message-ttl", _settings.MessageTtl } + }; + + await channel.QueueDeclareAsync( + queue: _settings.PushQueue, + durable: true, + exclusive: false, + autoDelete: false, + arguments: queueArgs, + cancellationToken: cancellationToken); + + await channel.QueueBindAsync( + queue: _settings.PushQueue, + exchange: _settings.Exchange, + routingKey: "push", + cancellationToken: cancellationToken); + + _logger.LogInformation("Queue 선언 및 바인딩 완료: {Queue} → {Exchange} (routing_key: push)", + _settings.PushQueue, _settings.Exchange); + + await channel.QueueDeclareAsync( + queue: _settings.ScheduleQueue, + durable: true, + exclusive: false, + autoDelete: false, + arguments: queueArgs, + cancellationToken: cancellationToken); + + await channel.QueueBindAsync( + queue: _settings.ScheduleQueue, + exchange: _settings.Exchange, + routingKey: "schedule", + cancellationToken: cancellationToken); + + _logger.LogInformation("Queue 선언 및 바인딩 완료: {Queue} → {Exchange} (routing_key: schedule)", + _settings.ScheduleQueue, _settings.Exchange); + } + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } -- 2.45.1 From 3fc3bb8144722ddb70966f6001a169e01de01be0 Mon Sep 17 00:00:00 2001 From: "seonkyu.kim" Date: Tue, 10 Feb 2026 19:15:42 +0900 Subject: [PATCH 2/2] =?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; } -- 2.45.1