Skip to content

Commit c548522

Browse files
committed
fix: 분산 시스템 미적용 및 데이터 페이로드 문제 수정
1 parent 48f34c6 commit c548522

File tree

5 files changed

+253
-21
lines changed

5 files changed

+253
-21
lines changed

ProjectVG.Api/Middleware/WebSocketMiddleware.cs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using ProjectVG.Application.Services.Session;
22
using ProjectVG.Infrastructure.Auth;
33
using ProjectVG.Infrastructure.Realtime.WebSocketConnection;
4+
using ProjectVG.Domain.Services.Server;
45
using System.Net.WebSockets;
56

67
namespace ProjectVG.Api.Middleware
@@ -12,19 +13,22 @@ public class WebSocketMiddleware
1213
private readonly ISessionManager _sessionManager;
1314
private readonly IWebSocketConnectionManager _connectionManager;
1415
private readonly IJwtProvider _jwtProvider;
16+
private readonly IServerRegistrationService? _serverRegistrationService;
1517

1618
public WebSocketMiddleware(
1719
RequestDelegate next,
1820
ILogger<WebSocketMiddleware> logger,
1921
ISessionManager sessionManager,
2022
IWebSocketConnectionManager connectionManager,
21-
IJwtProvider jwtProvider)
23+
IJwtProvider jwtProvider,
24+
IServerRegistrationService? serverRegistrationService = null)
2225
{
2326
_next = next;
2427
_logger = logger;
2528
_sessionManager = sessionManager;
2629
_connectionManager = connectionManager;
2730
_jwtProvider = jwtProvider;
31+
_serverRegistrationService = serverRegistrationService;
2832
}
2933

3034
public async Task InvokeAsync(HttpContext context)
@@ -113,6 +117,22 @@ private async Task RegisterConnection(Guid userId, WebSocket socket)
113117
_connectionManager.RegisterConnection(userIdString, connection);
114118
_logger.LogInformation("[WebSocketMiddleware] 로컬 WebSocket 연결 등록 완료: UserId={UserId}", userId);
115119

120+
// 3. 분산 시스템: 사용자-서버 매핑 저장 (Redis)
121+
if (_serverRegistrationService != null)
122+
{
123+
try
124+
{
125+
var serverId = _serverRegistrationService.GetServerId();
126+
await _serverRegistrationService.SetUserServerAsync(userIdString, serverId);
127+
_logger.LogInformation("[WebSocketMiddleware] 사용자-서버 매핑 저장 완료: UserId={UserId}, ServerId={ServerId}", userId, serverId);
128+
}
129+
catch (Exception mapEx)
130+
{
131+
_logger.LogWarning(mapEx, "[WebSocketMiddleware] 사용자-서버 매핑 저장 실패: UserId={UserId}", userId);
132+
// 매핑 저장 실패는 로그만 남기고 연결은 계속 진행
133+
}
134+
}
135+
116136
// [디버그] 등록 후 상태 확인
117137
var isSessionActive = await _sessionManager.IsSessionActiveAsync(userId);
118138
var hasLocalConnection = _connectionManager.HasLocalConnection(userIdString);
@@ -219,11 +239,25 @@ await socket.SendAsync(
219239
_logger.LogDebug("세션 관리자에서 세션 삭제 완료: {UserId}", userId);
220240
}
221241

222-
// 2. 로컬 WebSocket 연결 해제
242+
// 2. 분산 시스템: 사용자-서버 매핑 제거 (Redis)
243+
if (_serverRegistrationService != null)
244+
{
245+
try
246+
{
247+
await _serverRegistrationService.RemoveUserServerAsync(userId);
248+
_logger.LogDebug("사용자-서버 매핑 제거 완료: {UserId}", userId);
249+
}
250+
catch (Exception mapEx)
251+
{
252+
_logger.LogWarning(mapEx, "사용자-서버 매핑 제거 실패: {UserId}", userId);
253+
}
254+
}
255+
256+
// 3. 로컬 WebSocket 연결 해제
223257
_connectionManager.UnregisterConnection(userId);
224258
_logger.LogDebug("로컬 WebSocket 연결 해제 완료: {UserId}", userId);
225259

226-
// 3. WebSocket 소켓 정리
260+
// 4. WebSocket 소켓 정리
227261
if (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived) {
228262
await socket.CloseAsync(
229263
WebSocketCloseStatus.NormalClosure,

ProjectVG.Application/ApplicationServiceCollectionExtensions.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.Extensions.Configuration;
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.Logging;
4+
using StackExchange.Redis;
45
using ProjectVG.Application.Services.Auth;
56
using ProjectVG.Application.Services.Character;
67
using ProjectVG.Application.Services.Chat;
@@ -75,7 +76,19 @@ private static void AddChatServices(IServiceCollection services)
7576

7677
private static void AddDistributedServices(IServiceCollection services, IConfiguration configuration)
7778
{
78-
services.AddSingleton<IMessageBroker, DistributedMessageBroker>();
79+
// DistributedMessageBroker를 즉시 생성하도록 팩토리 패턴 사용
80+
services.AddSingleton<IMessageBroker>(serviceProvider =>
81+
{
82+
var redis = serviceProvider.GetRequiredService<IConnectionMultiplexer>();
83+
var connectionManager = serviceProvider.GetRequiredService<IWebSocketConnectionManager>();
84+
var serverRegistration = serviceProvider.GetRequiredService<ProjectVG.Domain.Services.Server.IServerRegistrationService>();
85+
var logger = serviceProvider.GetRequiredService<ILogger<DistributedMessageBroker>>();
86+
87+
logger.LogInformation("[DI] DistributedMessageBroker 팩토리에서 생성 시작");
88+
var broker = new DistributedMessageBroker(redis, connectionManager, serverRegistration, logger);
89+
logger.LogInformation("[DI] DistributedMessageBroker 팩토리에서 생성 완료");
90+
return broker;
91+
});
7992

8093
services.AddSingleton<ISessionManager>(serviceProvider =>
8194
{
@@ -85,6 +98,9 @@ private static void AddDistributedServices(IServiceCollection services, IConfigu
8598
});
8699

87100
AddWebSocketConnectionServices(services);
101+
102+
// MessageBroker 초기화를 강제하는 HostedService 등록
103+
services.AddHostedService<MessageBrokerInitializationService>();
88104
}
89105

90106
private static void AddWebSocketConnectionServices(IServiceCollection services)

ProjectVG.Application/Models/MessageBroker/BrokerMessage.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,25 @@ public class BrokerMessage
1515

1616
public static BrokerMessage CreateUserMessage(string userId, object payload, string? sourceServerId = null)
1717
{
18+
string payloadJson;
19+
20+
// WebSocketMessage인 경우 이미 올바른 형태이므로 그대로 직렬화
21+
if (payload is ProjectVG.Application.Models.WebSocket.WebSocketMessage wsMessage)
22+
{
23+
payloadJson = JsonSerializer.Serialize(wsMessage);
24+
}
25+
else
26+
{
27+
// 다른 객체의 경우 그대로 직렬화 (불필요한 래핑 방지)
28+
payloadJson = JsonSerializer.Serialize(payload);
29+
}
30+
1831
return new BrokerMessage
1932
{
2033
MessageType = "user_message",
2134
TargetUserId = userId,
2235
SourceServerId = sourceServerId,
23-
Payload = JsonSerializer.Serialize(payload),
36+
Payload = payloadJson,
2437
Headers = new Dictionary<string, string>
2538
{
2639
["content-type"] = "application/json"

ProjectVG.Application/Services/MessageBroker/DistributedMessageBroker.cs

Lines changed: 136 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using ProjectVG.Application.Services.Session;
66
using StackExchange.Redis;
77
using System.Collections.Concurrent;
8+
using System.Text.Json;
89

910
namespace ProjectVG.Application.Services.MessageBroker
1011
{
@@ -32,35 +33,58 @@ public DistributedMessageBroker(
3233
IServerRegistrationService serverRegistration,
3334
ILogger<DistributedMessageBroker> logger)
3435
{
35-
_redis = redis;
36-
_subscriber = redis.GetSubscriber();
37-
_connectionManager = connectionManager;
38-
_serverRegistration = serverRegistration;
39-
_logger = logger;
40-
_serverId = serverRegistration.GetServerId();
41-
42-
InitializeSubscriptions();
36+
_redis = redis ?? throw new ArgumentNullException(nameof(redis));
37+
_connectionManager = connectionManager ?? throw new ArgumentNullException(nameof(connectionManager));
38+
_serverRegistration = serverRegistration ?? throw new ArgumentNullException(nameof(serverRegistration));
39+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
40+
41+
_logger.LogInformation("[분산브로커] DistributedMessageBroker 생성자 시작");
42+
43+
try
44+
{
45+
_subscriber = redis.GetSubscriber();
46+
_serverId = serverRegistration.GetServerId();
47+
48+
_logger.LogInformation("[분산브로커] Redis 연결 상태: IsConnected={IsConnected}, ServerId={ServerId}",
49+
redis.IsConnected, _serverId);
50+
51+
InitializeSubscriptions();
52+
53+
_logger.LogInformation("[분산브로커] DistributedMessageBroker 생성 완료: ServerId={ServerId}", _serverId);
54+
}
55+
catch (Exception ex)
56+
{
57+
_logger.LogError(ex, "[분산브로커] DistributedMessageBroker 생성자 실패");
58+
throw;
59+
}
4360
}
4461

4562
private void InitializeSubscriptions()
4663
{
4764
try
4865
{
66+
_logger.LogInformation("[분산브로커] Redis 구독 초기화 시작: ServerId={ServerId}", _serverId);
67+
4968
// 이 서버로 오는 메시지 구독
5069
var serverChannel = $"{SERVER_CHANNEL_PREFIX}:{_serverId}";
70+
_logger.LogInformation("[분산브로커] 서버 채널 구독 시작: Channel={Channel}", serverChannel);
5171
_subscriber.Subscribe(serverChannel, OnServerMessageReceived);
72+
_logger.LogInformation("[분산브로커] 서버 채널 구독 완료: Channel={Channel}", serverChannel);
5273

5374
// 브로드캐스트 메시지 구독
75+
_logger.LogInformation("[분산브로커] 브로드캐스트 채널 구독 시작: Channel={Channel}", BROADCAST_CHANNEL);
5476
_subscriber.Subscribe(BROADCAST_CHANNEL, OnBroadcastMessageReceived);
77+
_logger.LogInformation("[분산브로커] 브로드캐스트 채널 구독 완료: Channel={Channel}", BROADCAST_CHANNEL);
5578

5679
// 사용자별 메시지 패턴 구독 (현재 서버에 연결된 사용자들만)
5780
// 사용자가 연결될 때 동적으로 구독하도록 변경 예정
5881

59-
_logger.LogInformation("분산 메시지 브로커 구독 초기화 완료: 서버 {ServerId}", _serverId);
82+
_logger.LogInformation("[분산브로커] 분산 메시지 브로커 구독 초기화 완료: ServerId={ServerId}", _serverId);
6083
}
6184
catch (Exception ex)
6285
{
63-
_logger.LogError(ex, "분산 메시지 브로커 구독 초기화 실패");
86+
_logger.LogError(ex, "[분산브로커] 분산 메시지 브로커 구독 초기화 실패: ServerId={ServerId}", _serverId);
87+
throw;
6488
}
6589
}
6690

@@ -92,14 +116,14 @@ public async Task SendToUserAsync(string userId, object message)
92116
return;
93117
}
94118

95-
// 3. 해당 서버로 메시지 전송
119+
// 3. 해당 서버로 메시지 전송 (서버별 채널 사용)
96120
var brokerMessage = BrokerMessage.CreateUserMessage(userId, message, _serverId);
97-
var userChannel = $"{USER_CHANNEL_PREFIX}:{userId}";
121+
var serverChannel = $"{SERVER_CHANNEL_PREFIX}:{targetServerId}";
98122

99123
_logger.LogInformation("[분산브로커] Redis Pub 시작: Channel={Channel}, TargetServerId={TargetServerId}, SourceServerId={SourceServerId}",
100-
userChannel, targetServerId, _serverId);
124+
serverChannel, targetServerId, _serverId);
101125

102-
await _subscriber.PublishAsync(userChannel, brokerMessage.ToJson());
126+
await _subscriber.PublishAsync(serverChannel, brokerMessage.ToJson());
103127

104128
_logger.LogInformation("[분산브로커] Redis Pub 완료: UserId={UserId}, TargetServerId={TargetServerId}", userId, targetServerId);
105129
}
@@ -245,9 +269,41 @@ private async void OnServerMessageReceived(RedisChannel channel, RedisValue mess
245269
}
246270

247271
// 서버별 메시지 처리 로직
248-
_logger.LogDebug("서버 메시지 수신: {MessageType}", brokerMessage.MessageType);
272+
_logger.LogInformation("[분산브로커] 서버 메시지 수신: MessageType={MessageType}, SourceServerId={SourceServerId}",
273+
brokerMessage.MessageType, brokerMessage.SourceServerId);
249274

250-
// TODO: 서버별 메시지 타입에 따른 처리 로직 구현
275+
// 사용자 메시지 처리
276+
if (brokerMessage.MessageType == "user_message" && !string.IsNullOrEmpty(brokerMessage.TargetUserId))
277+
{
278+
_logger.LogInformation("[분산브로커] 사용자 메시지 처리 시작: TargetUserId={TargetUserId}",
279+
brokerMessage.TargetUserId);
280+
281+
// 해당 사용자가 이 서버에 연결되어 있는지 확인
282+
if (_connectionManager.HasLocalConnection(brokerMessage.TargetUserId))
283+
{
284+
_logger.LogInformation("[분산브로커] 원본 Payload JSON: {PayloadJson}", brokerMessage.Payload);
285+
286+
// 원본 JSON 문자열을 직접 사용하여 메시지 전달
287+
await SendLocalMessageAsJson(brokerMessage.TargetUserId, brokerMessage.Payload);
288+
289+
_logger.LogInformation("[분산브로커] 사용자 메시지 전달 완료: TargetUserId={TargetUserId}",
290+
brokerMessage.TargetUserId);
291+
}
292+
else
293+
{
294+
_logger.LogWarning("[분산브로커] 대상 사용자가 이 서버에 연결되어 있지 않음: TargetUserId={TargetUserId}, ServerId={ServerId}",
295+
brokerMessage.TargetUserId, _serverId);
296+
}
297+
}
298+
else if (brokerMessage.MessageType == "server_message")
299+
{
300+
// 다른 서버별 메시지 타입 처리 (향후 확장)
301+
_logger.LogDebug("[분산브로커] 서버 메시지 처리: {MessageType}", brokerMessage.MessageType);
302+
}
303+
else
304+
{
305+
_logger.LogWarning("[분산브로커] 알 수 없는 메시지 타입: {MessageType}", brokerMessage.MessageType);
306+
}
251307
}
252308
catch (Exception ex)
253309
{
@@ -320,6 +376,64 @@ private async void OnBroadcastMessageReceived(RedisChannel channel, RedisValue m
320376
}
321377
}
322378

379+
private async Task SendLocalMessageAsJson(string userId, string payloadJson)
380+
{
381+
if (string.IsNullOrEmpty(payloadJson))
382+
{
383+
_logger.LogWarning("[분산브로커] SendLocalMessageAsJson: Payload가 비어있습니다. UserId={UserId}", userId);
384+
return;
385+
}
386+
387+
_logger.LogInformation("[분산브로커] SendLocalMessageAsJson 시작: UserId={UserId}, PayloadLength={PayloadLength}",
388+
userId, payloadJson.Length);
389+
390+
try
391+
{
392+
// 원본 JSON이 이미 WebSocketMessage 형태인지 확인
393+
using var document = JsonDocument.Parse(payloadJson);
394+
var root = document.RootElement;
395+
396+
string messageText;
397+
398+
// WebSocketMessage 구조인지 확인 (type과 data 필드가 있는지)
399+
if (root.TryGetProperty("type", out var typeProperty) &&
400+
root.TryGetProperty("data", out var dataProperty))
401+
{
402+
// 이미 WebSocketMessage 형태이므로 그대로 사용
403+
messageText = payloadJson;
404+
_logger.LogInformation("[분산브로커] WebSocketMessage 형태 감지: Type={Type}", typeProperty.GetString());
405+
}
406+
else
407+
{
408+
// 일반 객체이므로 WebSocketMessage로 래핑 (예상되지 않는 케이스)
409+
_logger.LogWarning("[분산브로커] 예상하지 못한 JSON 구조, WebSocketMessage로 래핑: UserId={UserId}", userId);
410+
var wrappedMessage = new WebSocketMessage("message", root);
411+
messageText = System.Text.Json.JsonSerializer.Serialize(wrappedMessage);
412+
}
413+
414+
_logger.LogInformation("[분산브로커] 최종 전송 메시지: {MessageText}", messageText);
415+
416+
var success = await _connectionManager.SendTextAsync(userId, messageText);
417+
if (success)
418+
{
419+
_logger.LogInformation("[분산브로커] JSON 메시지 전송 완료: UserId={UserId}", userId);
420+
}
421+
else
422+
{
423+
_logger.LogWarning("[분산브로커] JSON 메시지 전송 실패: UserId={UserId}", userId);
424+
}
425+
}
426+
catch (JsonException ex)
427+
{
428+
_logger.LogError(ex, "[분산브로커] JSON 파싱 실패: UserId={UserId}, Payload={Payload}", userId, payloadJson);
429+
}
430+
catch (Exception ex)
431+
{
432+
_logger.LogError(ex, "[분산브로커] SendLocalMessageAsJson 실패: UserId={UserId}", userId);
433+
throw;
434+
}
435+
}
436+
323437
private async Task SendLocalMessage(string userId, object? message)
324438
{
325439
if (message == null)
@@ -335,12 +449,18 @@ private async Task SendLocalMessage(string userId, object? message)
335449
{
336450
string messageText;
337451

452+
// WebSocketMessage는 이미 올바른 형태이므로 그대로 직렬화
338453
if (message is WebSocketMessage wsMessage)
339454
{
340455
messageText = System.Text.Json.JsonSerializer.Serialize(wsMessage);
456+
_logger.LogInformation("[분산브로커] WebSocketMessage 직렬화: UserId={UserId}, Type={Type}",
457+
userId, wsMessage.Type);
341458
}
342459
else
343460
{
461+
// 다른 객체는 WebSocketMessage로 래핑 (하지만 ChatSuccessHandler에서는 이미 래핑됨)
462+
_logger.LogWarning("[분산브로커] 예상하지 못한 객체 타입: {MessageType}, UserId={UserId}",
463+
message.GetType().Name, userId);
344464
var wrappedMessage = new WebSocketMessage("message", message);
345465
messageText = System.Text.Json.JsonSerializer.Serialize(wrappedMessage);
346466
}

0 commit comments

Comments
 (0)