1. 아키텍처 개요
Gateway는 클라이언트와 서버(Game Server) 사이의 Single Entry Point이다.
Nginx나 HAProxy 같은 범용 LB와 달리, 세션의 상태를 파악하고 세션 유지(Stickiness)를 수행하는 커스텀 L7 프록시이다.

서버가 지속적으로 Redis에 하트비트를 보내 살아있음을 알린다.
Gateway는 Redis에서 서버 상태를 읽고 적절히 트래픽을 분산한다.
2. 주요 책임
- L7 Load Balancing: 서버들의 부하(접속자 수)를 모니터링하여 최적의 서버로 트래픽을 분산한다.
- Session Binding (Stickiness): 유저가 잠시 연결이 끊겼다 재접속해도, 로직 처리를 위해 반드시 이전에 접속했던 서버 인스턴스로 보내준다.
- 지금의 단순한 채팅 서버에선 필요가 없는 기능이긴 하다.
- Connection Management: 클라이언트 연결(
GatewayConnection)과 백엔드 연결(BackendSession)을 1:1로 매핑하여 관리한다. (Note: 현재 구현은 Connection Pooling이 아닌, 1:1 Bridging 방식)
3. 연결 모델
게이트웨이는 하나의 논리적 클라이언트에 대해 두 개의 물리적 소켓을 유지하고 브릿징한다.
3.1. GatewayConnection: Client-Side
- 클라이언트와 직접 연결되는 소켓.
Hive(I/O Engine)에 의해 관리된다.- 패킷이 들어오면
BackendSession으로send()한다.
3.2. BackendSession: Server-Side
- 실제 게임 서버와 연결되는 소켓.
GatewayApp내부 클래스로 구현되어 있다.- 패킷이 들어오면
GatewayConnection으로 반사한다.
// gateway/src/gateway_connection.cpp
void GatewayConnection::on_read(const uint8_t* data, size_t length) {
if (!backend_session_) {
// 첫 패킷이면(아직 목적지 서버가 없으면) 로드밸런싱 수행
open_backend_session();
}
// 패킷 전달 (Forwarding)
backend_session_->send({data, data + length});
}
4. 로드밸런싱 전략
GatewayApp::select_best_server는 다음 우선순위로 타겟 서버를 결정한다.
- Stickiness Check: 이 유저가 원래 있던 서버가 있는가? (
SessionDirectory) - Liveness Check: 그 서버가 지금 살아있는가? (
BackendRegistry) - Least Connections: 1, 2가 아니라면, 현재 가장 널널한(접속자가 적은) 서버를 선택.
// gateway/src/gateway_app.cpp
std::optional<pair<string, uint16>> GatewayApp::select_best_server(const string& client_id) {
auto instances = backend_registry_->list_instances();
// 1. 유저의 기존 서버 확인 (Redis 조회)
if (auto backend_id = session_directory_->find_backend(client_id)) {
// ...Alive Check...
return {host, port}; // Sticky Connection
}
// 2. 가장 접속자 적은(Least Connections) 서버 선택
std::sort(available.begin(), available.end(), [](auto& a, auto& b) {
return a.active_sessions < b.active_sessions;
});
return {available.front().host, available.front().port};
}
5. Session Stickiness
Redis를 활용한 분산 세션 관리 시스템이다.
5.1. The "Split-Brain" Strategy (Cache + Redis)
단순 Redis 조회는 매번 RTT가 발생하여 느릴 수 있다. SessionDirectory는 Local Mutex Cache와 Redis를 혼용한다.
| Layer | 역할 | 특징 |
|---|---|---|
| L1: Local Cache | std::unordered_map |
락이 걸려있지만 가장 빠름. 짧은 TTL. |
| L2: Redis | set_if_not_exists (SETNX) |
다른 게이트웨이와 정보 공유. Single Source of Truth. |
// gateway/src/session_directory.cpp
std::optional<std::string> SessionDirectory::find_backend(const std::string& client_id) {
// 1. 로컬 캐시 확인
{
std::lock_guard lk(mutex_);
if (cache_.has(client_id)) return cache_.get(client_id);
}
// 2. Redis 확인 (Cache Miss)
auto value = redis_->get("gateway/session/" + client_id);
if (value) {
// 캐시 채우기 (Read-Repair)
update_cache(client_id, *value);
}
return value;
}
- Design Decision: 게이트웨이 인스턴스가 여러 대여도, 유저는 어떤 게이트웨이로 접속하든 항상 자신의 서버로 라우팅된다.
6. 부트스트랩
GatewayApp 생성 시 인프라를 설정한다.
| 환경 변수 | 기본값 | 설명 |
|---|---|---|
GATEWAY_LISTEN |
0.0.0.0:6000 |
클라이언트를 기다릴 주소 |
GATEWAY_ID |
gateway-default |
게이트웨이 식별자 (로그/메트릭용) |
REDIS_URI |
127.0.0.1:6379 |
서버 레지스트리 및 세션 정보 조회용 |
METRICS_PORT |
- | Prometheus Exporter 포트 |
7. 트래픽 흐름
- Client Connect: 클라이언트와 TCP 연결이 수립된다. ->
GatewayConnection생성. - First Packet Inspection: 첫 데이터 패킷을 분석한다.
- Login Frame:
MSG_LOGIN_REQ헤더가 보이면 유저 ID를 추출한다. - Legacy Token:
token:client_id포맷인지 확인한다. - Anonymous: 둘 다 아니면 익명 접속으로 간주한다.
- 현재는 무조건 익명 접속으로 진행된다.
- Login Frame:
- Routing:
GatewayConnection이GatewayApp::create_backend_session을 호출한다. - Backend Selection:
select_best_server가 Redis를 뒤져서 목적지 서버(예:10.0.0.4:5000)를 찾는다. - Connect Backend:
BackendSession을 만들고async_connect를 시도한다. - Bridge Established: 연결이 맺어지면, 클라이언트가 보낸 첫 패킷을 백엔드로
flush한다. - Proxying: 이후 양쪽에서 오는 모든 데이터(
on_read)를 상대방에게send한다.- 7.1. Graceful Shutdown
GatewayApp은SIGINT,SIGTERM시그널을 감지하면stop()을 호출하여 모든 세션을 정상 종료하고 리소스를 정리한다.
- 7.1. Graceful Shutdown
8. Observability
게이트웨이 자체도 모니터링이 필요하다.
- Log: 주요 이벤트(Backend Connect/Close, Auth Fail)를 콘솔에 남긴다.
- Metrics:
http://gateway:9091/metricsgateway_sessions_active: 현재 중계 중인 연결 수.
// gateway/src/gateway_app.cpp
metrics_server_ = std::make_unique<MetricsHttpServer>(port, [this]() {
// 세션 맵의 크기를 실시간으로 반환
return "gateway_sessions_active " + std::to_string(sessions_.size());
});
'Study > C++ & C#' 카테고리의 다른 글
| AI로 MSA 서버 만들어 보기 #4 : Write-Behind (0) | 2025.12.09 |
|---|---|
| AI로 MSA 서버 만들어 보기 #2 : Server (0) | 2025.12.07 |
| AI로 MSA 서버 만들어 보기 #1 : Core (0) | 2025.12.03 |
| [C++] 채팅서버에 DB 실장 (0) | 2024.05.14 |
| [C#] ###Clicker 동작 개선 (0) | 2024.05.05 |