endGame 함수)roomId를 기반으로 진행 중인 gameId를 가져와 게임 데이터 조회winningTeam) 결정finalState)를 구성하여 각 플레이어의 역할 및 생존 여부 저장gameResultKey 존재 여부 확인 후 Redis 트랜잭션 처리gameResults 채널에 Pub/Sub 전송GameResultsSubscriber)gameResults 채널을 Redis에서 구독하여 게임 결과 수신gameResultKey 존재 여부를 확인하여 중복 저장 방지GameResultsService를 통해 RDS에 저장gameResultKey 삭제 가능GameResultsService)gameId와 winningTeam을 기반으로 결과를 저장citizen, police, doctor)과 마피아를 비교하여 승패 계산win 또는 lose)와 생존 여부를 GameResult 엔티티에 저장timestamp 기준으로 조회하는 기능 구현Pub/Sub 패턴을 적용하여 분산 서버에서 게임 결과를 효율적으로 전송할 수 있었다.Redis exists 체크와 트랜잭션 처리를 통해 데이터 정합성을 유지했다.분산 서버 중에서 게임 서버
const gameResult = {
roomId,
gameId,
winningTeam,
finalState,
timestamp: new Date().toISOString(),
};
const multi = this.redisClient.multi();
multi.set(gameResultKey, JSON.stringify(gameResult), 'EX', 86400); // 24시간 유지
multi.publish('gameResults', JSON.stringify(gameResult)); // Redis Pub/Sub 전송
await multi.exec(); // 트랜잭션 실행
console.log(`게임 결과가 저장됨 (gameId: ${gameId})`);
엔드 게임 로직에 퍼블리시 코드를 만들어 줬다.
그리고 api 서버에서 구독파일을 만들어서 받아준다.
@Injectable()
export class GameResultsSubscriber implements OnModuleInit {
private redisSubscriber: Redis;
private redisClient: Redis;
constructor(private readonly gameResultsService: GameResultsService) {
// 일반 Redis 클라이언트 (데이터 조회 용)
this.redisClient = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
});
// 구독 전용 Redis 클라이언트
this.redisSubscriber = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
});
}
async onModuleInit() {
console.log('게임 결과 Redis 구독 시작');
this.redisSubscriber.subscribe('gameResults', (err, count) => {
if (err) {
console.error('Redis 구독 실패:', err);
} else {
console.log(`게임 결과 채널 구독 중... (${count})`);
}
});
this.redisSubscriber.on('message', async (channel, message) => {
if (channel === 'gameResults') {
console.log(`게임 결과 수신: ${message}`);
const gameResult = JSON.parse(message);
const gameId = gameResult.gameId;
const gameResultKey = `gameResult:${gameId}`;
// 이미 저장된 게임 결과인지 확인 (중복 방지)
const isAlreadyStored = await this.redisClient.exists(gameResultKey);
if (!isAlreadyStored) {
console.warn(`게임 결과가 Redis에 없음 (gameId: ${gameId}), 무시.`);
return;
}
// 게임 결과 RDS에 저장
await this.gameResultsService.saveGameResult(gameResult);
console.log(`게임 결과가 RDS에 저장됨 (gameId: ${gameId}).`);
// RDS에 저장 완료 후, Redis에서 해당 게임 결과 삭제 (필요 시)
// await this.redisClient.del(gameResultKey);
}
});
}
}
또한 게임 결과 서비스 로직에서 받은 값을 저장해주면 된다.
// 게임 결과 저장 (플레이어 개별 저장)
async saveGameResult(gameData: any): Promise<void> {
const gameId = gameData.gameId;
const winningTeam = gameData.winningTeam;
// 시민팀에 속하는 역할 목록 (게임 서버의 로직과 일치해야 함)
const citizenRoles = ['citizen', 'police', 'doctor'];
const results = gameData.finalState.players.map((player) => {
// 플레이어가 이긴 팀에 속하는지 확인
const isWinner =
(winningTeam === 'citizens' && citizenRoles.includes(player.role)) ||
(winningTeam === 'mafia' && player.role === 'mafia');
return {
gameId,
userId: player.userId,
role: player.role,
alive: player.alive ? 'alive' : 'dead',
winningTeam, // 올바르게 전달
result: isWinner ? 'win' : 'lose', // 승패 계산 수정
};
});
await this.gameResultRepository.save(results);
console.log(` 게임 결과 저장 완료 (gameId: ${gameId})`);
}
이렇게 만들면 분산 서버에서 pub/sub 를 사용할 수 있게 된다.