Skip to content

Commit 5f1f9dc

Browse files
committed
feat: Redis 세션 유지 시작 지정
1 parent eaf1959 commit 5f1f9dc

File tree

14 files changed

+800
-43
lines changed

14 files changed

+800
-43
lines changed

ProjectVG.Api/Middleware/WebSocketMiddleware.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ await socket.SendAsync(
160160
WebSocketMessageType.Text,
161161
true,
162162
cancellationTokenSource.Token);
163+
164+
// 세션 하트비트 업데이트 (Redis TTL 갱신)
165+
try {
166+
await _webSocketService.UpdateSessionHeartbeatAsync(userId);
167+
}
168+
catch (Exception heartbeatEx) {
169+
_logger.LogWarning(heartbeatEx, "세션 하트비트 업데이트 실패: {UserId}", userId);
170+
}
163171
}
164172
}
165173
}

ProjectVG.Application/Services/MessageBroker/DistributedMessageBroker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Microsoft.Extensions.Logging;
22
using ProjectVG.Application.Models.MessageBroker;
33
using ProjectVG.Application.Models.WebSocket;
4-
using ProjectVG.Application.Services.Server;
4+
using ProjectVG.Domain.Services.Server;
55
using ProjectVG.Application.Services.WebSocket;
66
using StackExchange.Redis;
77

ProjectVG.Application/Services/WebSocket/DistributedWebSocketManager.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,29 @@ public bool IsSessionActive(string userId)
112112
{
113113
return _connectionRegistry.IsConnected(userId);
114114
}
115+
116+
public async Task UpdateSessionHeartbeatAsync(string userId)
117+
{
118+
try
119+
{
120+
_logger.LogDebug("[분산WebSocket] 세션 하트비트 업데이트: UserId={UserId}", userId);
121+
122+
// Redis 세션 TTL 갱신
123+
if (_sessionStorage is ProjectVG.Infrastructure.Persistence.Session.RedisSessionStorage redisStorage)
124+
{
125+
await redisStorage.HeartbeatAsync(userId);
126+
}
127+
else
128+
{
129+
// InMemory 세션의 경우 별도 하트비트가 필요 없음
130+
_logger.LogDebug("[분산WebSocket] InMemory 세션은 하트비트 갱신 불필요: UserId={UserId}", userId);
131+
}
132+
}
133+
catch (Exception ex)
134+
{
135+
_logger.LogError(ex, "[분산WebSocket] 세션 하트비트 업데이트 실패: UserId={UserId}", userId);
136+
throw;
137+
}
138+
}
115139
}
116140
}

ProjectVG.Application/Services/WebSocket/IWebSocketManager.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,25 @@ public interface IWebSocketManager
88
/// WebSocket 연결을 생성하고 초기화합니다
99
/// </summary>
1010
Task<string> ConnectAsync(string userId);
11-
11+
1212
/// <summary>
1313
/// WebSocket 메시지를 전송합니다
1414
/// </summary>
1515
Task SendAsync(string userId, WebSocketMessage message);
16-
16+
1717
/// <summary>
1818
/// WebSocket 연결을 종료합니다
1919
/// </summary>
2020
Task DisconnectAsync(string userId);
21-
21+
2222
/// <summary>
2323
/// 세션이 활성 상태인지 확인합니다
2424
/// </summary>
2525
bool IsSessionActive(string userId);
26+
27+
/// <summary>
28+
/// 세션 하트비트를 업데이트합니다 (Redis TTL 갱신)
29+
/// </summary>
30+
Task UpdateSessionHeartbeatAsync(string userId);
2631
}
2732
}

ProjectVG.Application/Services/WebSocket/WebSocketManager.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,12 @@ public bool IsSessionActive(string userId)
7575
{
7676
return _connectionRegistry.IsConnected(userId);
7777
}
78+
79+
public Task UpdateSessionHeartbeatAsync(string userId)
80+
{
81+
// 로컬 WebSocket 매니저는 별도 하트비트 업데이트가 필요 없음
82+
_logger.LogDebug("로컬 WebSocket 매니저 하트비트 (no-op): {UserId}", userId);
83+
return Task.CompletedTask;
84+
}
7885
}
7986
}

ProjectVG.Common/Models/Session/SessionInfo.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
namespace ProjectVG.Common.Models.Session
22
{
3-
public record SessionInfo
3+
public class SessionInfo
44
{
5-
public required string SessionId { get; init; }
6-
public string? UserId { get; init; }
7-
public DateTime ConnectedAt { get; init; } = DateTime.UtcNow;
5+
public required string SessionId { get; set; }
6+
public string? UserId { get; set; }
7+
public DateTime ConnectedAt { get; set; } = DateTime.UtcNow;
8+
public DateTime LastActivity { get; set; } = DateTime.UtcNow;
89
}
910
}
1011

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace ProjectVG.Domain.Models.Server
2+
{
3+
public class ServerInfo
4+
{
5+
public string ServerId { get; set; } = string.Empty;
6+
public DateTime StartedAt { get; set; }
7+
public DateTime LastHeartbeat { get; set; }
8+
public int ActiveConnections { get; set; }
9+
public string Status { get; set; } = "healthy";
10+
public string? Environment { get; set; }
11+
public string? Version { get; set; }
12+
13+
public ServerInfo()
14+
{
15+
}
16+
17+
public ServerInfo(string serverId)
18+
{
19+
ServerId = serverId;
20+
StartedAt = DateTime.UtcNow;
21+
LastHeartbeat = DateTime.UtcNow;
22+
ActiveConnections = 0;
23+
Status = "healthy";
24+
}
25+
26+
public void UpdateHeartbeat()
27+
{
28+
LastHeartbeat = DateTime.UtcNow;
29+
}
30+
31+
public void UpdateConnectionCount(int count)
32+
{
33+
ActiveConnections = count;
34+
}
35+
36+
public bool IsHealthy(TimeSpan timeout)
37+
{
38+
return DateTime.UtcNow - LastHeartbeat < timeout;
39+
}
40+
}
41+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using ProjectVG.Domain.Models.Server;
2+
3+
namespace ProjectVG.Domain.Services.Server
4+
{
5+
public interface IServerRegistrationService
6+
{
7+
/// <summary>
8+
/// 서버를 등록합니다
9+
/// </summary>
10+
Task RegisterServerAsync();
11+
12+
/// <summary>
13+
/// 서버 등록을 해제합니다
14+
/// </summary>
15+
Task UnregisterServerAsync();
16+
17+
/// <summary>
18+
/// 헬스체크를 수행합니다
19+
/// </summary>
20+
Task SendHeartbeatAsync();
21+
22+
/// <summary>
23+
/// 현재 서버 ID를 가져옵니다
24+
/// </summary>
25+
string GetServerId();
26+
27+
/// <summary>
28+
/// 활성 서버 목록을 가져옵니다
29+
/// </summary>
30+
Task<IEnumerable<ServerInfo>> GetActiveServersAsync();
31+
32+
/// <summary>
33+
/// 특정 사용자가 연결된 서버 ID를 가져옵니다
34+
/// </summary>
35+
Task<string?> GetUserServerAsync(string userId);
36+
37+
/// <summary>
38+
/// 사용자와 서버 매핑을 설정합니다
39+
/// </summary>
40+
Task SetUserServerAsync(string userId, string serverId);
41+
42+
/// <summary>
43+
/// 사용자와 서버 매핑을 제거합니다
44+
/// </summary>
45+
Task RemoveUserServerAsync(string userId);
46+
47+
/// <summary>
48+
/// 오프라인 서버들을 정리합니다
49+
/// </summary>
50+
Task CleanupOfflineServersAsync();
51+
}
52+
}

ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ private static void AddDistributedSystemServices(IServiceCollection services, IC
223223
if (distributedEnabled)
224224
{
225225
// 분산 시스템이 활성화된 경우에만 등록
226-
services.AddScoped<IServerRegistrationService, RedisServerRegistrationService>();
227-
services.AddHostedService<ServerLifecycleService>();
226+
services.AddScoped<ProjectVG.Domain.Services.Server.IServerRegistrationService, ProjectVG.Infrastructure.Services.Server.RedisServerRegistrationService>();
227+
services.AddHostedService<ProjectVG.Infrastructure.Services.Server.ServerLifecycleService>();
228228
229229
Console.WriteLine("분산 시스템 모드 활성화");
230230
}

0 commit comments

Comments
 (0)