게임서버의 타이머 관리
넥슨에 재직중인 선배님과 진득하게 얘기할 기회가 있어서 그중에 프로젝트에 쓰기 위해서 물어본 게 있었다.
현업에서는 게임서버의 타이머를 어떻게 관리하나요?
M 게임에서 스킬이나 쿨타임쪽을 담당하진 않지만, 알기로는 타이머를 담당하는 쓰레드를 따로 두고 처리한다고 하셨다.
class Timer {
...
};
void TimerCheckThread() {
while (true) {
Timer::TimeOutCheck();
Timer::ProcessTimers();
Sleep(1000);
}
}
int main() {
IOCPInit();
std::thread timer_thread(TimerCheckThread);
timer_thread.join();
return 0;
}
이.. 이렇게.. 하는거 맞나...?
일단 잘 굴러가니.. 많은 도움이 된거 같다.
사실 게임 서버의 성능 테스트는 어떤 지표를 가지고 평가해야할지도 물어봤어야 했는데 갑작스러운 CS 질문(...)에 무너져서 뇌가 녹았다.
Session 객체의 포인터를 정수형(intptr_t)으로 변환
포인터를 안전하게 정수형으로 변환하고 되돌릴 수 있도록 설계
intptr_t timerId = reinterpret_cast<intptr_t>(session);
intptr_t는 C++의 정수형 타입, 포인터 크기와 동일한 크기를 가진다. (32bit 환경 intptr_t는 32bit 정수
Session 객체에 대해서 고유한 식별값을 TimerId로 가져야한다.
그러기 위해서 메모리 상에서 고유한 주소를 가지는 객체 포인터값을 Id로 사용하는거다.
게임서버의 PostegreSQL 도입 시도 (↔ MySQL 정합성)
사용자 데이터는 PostegreSQL 데이터베이스를 사용하고, 인게임 데이터는 MySQL을 사용한다.
여기서 말하는 인게임 데이터는 게임에서 사용되는 아이템의 정보, 능력치, 각 챔프별 고유 데이터값과 스킬 계수 등이다.
게임 업데이트를 통해 주기적으로 버프, 너프 등의 변동이 생길 수 있기에 외부 데이터베이스에서 따로 관리해야했다.
PostegreSQL이 굉장히 현대적이고 다양한 타입형을 지원해서 BSON 타입으로 데이터를 관리하기 위해 채택했다. // todo
하지만 게임 서버와 연동하기 위한 connector가 넘 구리다. // todo
전적 통계에서도 각 챔프의 이름 등이 제공되어야 하는 것처럼 게임 서비스와 백엔드간 중복되는 엔티티가 존재한다.
이들간의 데이터 정합성을 유지하기 위해서 백엔드에서 게임 데이터베이스(MySQL)도 함께 연결했다.
온전히 조회만을 목적으로 하기에 데이터의 일관성을 해치지 않고, 애플리케이션 시작시에 요청을 보내서 HashMap에 기록해두기 때문에 잦은 요청으로 인해 데이터베이스 성능을 저하시킬 요인도 없다고 생각한다.
동일 계정에 대한 동시접속
근데 게임 시작과 게임 종료시 Code가 백엔드에 남지 않나...?
이론상 동시 접속이 되면 안되는 로직인데 …
return reactiveRedisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, Duration.ofSeconds(10))
.flatMap(locked -> {
if (!locked) {
return Mono.just("fail");
}
return getUserResponse(userId)
.flatMap(userResponse -> handleMatchResponse(userResponse, queue, userId))
.doFinally(signal -> releaseLock(lockKey, lockValue)
.subscribe());
});
매칭 큐에 등록할때, 다음과 같이 Redis를 이용해서 Lock으로 검사하고 있다.
하지만, 이미 게임중인 상태인 경우에는 redis에서 매칭을 위해 등록된 데이터를 삭제한 뒤인 가능성도 염두해야한다.
따라서 직접 게임중인지를 판별하는 code 필드값을 membership 엔티티로부터 받아와야한다.
return getUserHasCode(userId)
.flatMap(hasCode -> {
if (hasCode) {
return getUserResponse(userId)
.flatMap(userResponse -> handleMatchResponse(userResponse, queue, userId));
} else return Mono.just("fail");
})
.doFinally(signal -> releaseLock(lockKey, lockValue)
.subscribe());
매치메이킹 - 재접속 금지 정책
매칭중에 게임 나가면 매칭 취소하거나, 픽창에서도 취소(dodge)시켜야 한다.
1. 매칭중에 나가는 경우 : getRank 함수 안들어오면 그냥 매칭 큐에서 지우기
2. MatchQueueService의 expireTime은 default=3으로 지정했고, 클라이언트는 1초마다 요청을 보낸다.
401 불충분한 인가의 경우, 재요청을 보내는데 이래도 getRank()가 호출되지 않아서 기존에 설정된 3초를 지나면 매칭 큐에서 지워진다.
3. 챔프 픽창에서 나가는 경우 : 게임서버에서 그냥 연결 다 끊어버리고 방 지우기
'project > wargame' 카테고리의 다른 글
포탑의 공격 우선순위와 병종 시스템 개선 (feat. Kafka 메시지 유실) (0) | 2025.01.17 |
---|---|
매칭 서버의 대규모 트래픽 시험 검증하기 (feat. Kafka 성능 튜닝) (2) | 2025.01.04 |
사용자 경험 개선을 위한 시도 (Banner 구현, Addressable 패치) (0) | 2025.01.04 |
CQRS 패턴을 이용한 데이터 쿼리 - 게임 전적과 통계 구현 (0) | 2025.01.03 |
MSA? 이길 수 없다면 합류해라 (feat. Kafka IPC 트러블슈팅) (1) | 2025.01.03 |