관리 메뉴

Mini

24. 11. 17. 개발일지 redis 탐색 본문

개발일지

24. 11. 17. 개발일지 redis 탐색

Mini_96 2024. 11. 18. 00:12

* 로직 이해하기

  • createRoom 할때 일어나는 일

set에 살아있는 방번호를 관리한다.
hash에 방에 대한 정보를 저장한다.

 

  • joinRoom시 일어나는 일

 

해쉬가 생긴다. 플레이어별로. 위치, 게임아이디 를 저장한다.
이건뭐지 이벤트 인가?
해당 방의 참가자들의 목록을 관리하는 set
방의 참가자들의 점수를 관리하는 sorted set

  • start Game 누르면 나머지 event들이 알아서 발생되는군
  • start Game 누르면 생기는일 

  • 퀴즈, 정답을 레디스에 올리는구나

이벤트를 구독중이었는데, Start라는 이벤트가 레디스에서 오는것 같다.
그러네 Player별로 각각 Changes를 구독중이고, 레디스에서 여기로 이벤트를 발행하는것 같다.

 

 

* gameService 주석

@Injectable()
export class GameService {
  /**
   * 게임 서비스에서 사용되는 Redis 명령어 설명
   * 
   * Redis 키 구조:
   * - Room:{gameId} : 게임 방 정보를 저장하는 해시
   * - Player:{clientId} : 플레이어 정보를 저장하는 해시
   * - Room:{gameId}:Players : 게임 방의 플레이어 목록을 저장하는 Set
   * - Room:{gameId}:Leaderboard : 게임 방의 리더보드를 저장하는 Sorted Set
   * - Room:{gameId}:Quiz:Set : 게임 방의 퀴즈 ID 목록을 저장하는 Set
   */

  async startGame(startGameDto: StartGameDto, clientId: string): Promise<void> {
    const { gameId } = startGameDto;
    const roomKey = REDIS_KEY.ROOM(gameId);

    /**
     * Redis HGETALL 명령어
     * @description 해시에 저장된 모든 필드와 값을 가져옴
     * @example 
     * HGETALL Room:123
     * 결과: { host: "client1", status: "waiting", title: "Game1", ... }
     */
    const room = await this.redis.hgetall(roomKey);

    // 기존 퀴즈 데이터 초기화
    const prevQuizList = await this.redis.smembers(REDIS_KEY.ROOM_QUIZ_SET(gameId));
    for (const prevQuiz of prevQuizList) {
      /**
       * Redis DEL 명령어
       * @description 지정된 키를 삭제
       * @example DEL Room:123:Quiz:1
       */
      await this.redis.del(REDIS_KEY.ROOM_QUIZ(gameId, prevQuiz));
      await this.redis.del(REDIS_KEY.ROOM_QUIZ_CHOICES(gameId, prevQuiz));
    }

    /**
     * Redis SADD 명령어
     * @description Set에 하나 이상의 멤버를 추가
     * @example 
     * SADD Room:123:Quiz:Set quiz1 quiz2 quiz3
     * 결과: 추가된 멤버 수
     */
    await this.redis.sadd(
      REDIS_KEY.ROOM_QUIZ_SET(gameId),
      ...selectedQuizList.map((quiz) => quiz.id)
    );

    /**
     * Redis HMSET 명령어
     * @description 해시에 여러 필드-값 쌍을 설정
     * @example
     * HMSET Room:123:Quiz:1 quiz "Question?" answer "2" limitTime "30"
     */
    await this.redis.hmset(REDIS_KEY.ROOM_QUIZ(gameId, quiz.id), {
      quiz: quiz.quiz,
      answer: quiz.choiceList.find((choice) => choice.isAnswer).order,
      limitTime: quiz.limitTime.toString(),
      choiceCount: quiz.choiceList.length.toString()
    });

    // 리더보드 초기화
    /**
     * Redis ZRANGE 명령어
     * @description Sorted Set의 범위 내 멤버를 가져옴
     * @example
     * ZRANGE Room:123:Leaderboard 0 -1
     * 결과: ["player1", "player2", ...]
     */
    const leaderboard = await this.redis.zrange(REDIS_KEY.ROOM_LEADERBOARD(gameId), 0, -1);

    /**
     * Redis ZADD 명령어
     * @description Sorted Set에 점수와 함께 멤버를 추가
     * @example
     * ZADD Room:123:Leaderboard 0 player1
     */
    for (const playerId of leaderboard) {
      await this.redis.zadd(REDIS_KEY.ROOM_LEADERBOARD(gameId), 0, playerId);
    }
  }

  async disconnect(clientId: string): Promise<void> {
    const playerKey = REDIS_KEY.PLAYER(clientId);
    const playerData = await this.redis.hgetall(playerKey);

    /**
     * Redis SREM 명령어
     * @description Set에서 지정된 멤버를 제거
     * @example
     * SREM Room:123:Players player1
     */
    await this.redis.srem(REDIS_KEY.ROOM_PLAYERS(playerData.gameId), clientId);

    /**
     * Redis ZREM 명령어
     * @description Sorted Set에서 지정된 멤버를 제거
     * @example
     * ZREM Room:123:Leaderboard player1
     */
    await this.redis.zrem(REDIS_KEY.ROOM_LEADERBOARD(playerData.gameId), clientId);

    /**
     * Redis SRANDMEMBER 명령어
     * @description Set에서 무작위 멤버를 가져옴
     * @example
     * SRANDMEMBER Room:123:Players
     * 결과: "player2"
     */
    const newHost = await this.redis.srandmember(REDIS_KEY.ROOM_PLAYERS(playerData.gameId));

    /**
     * Redis SET 명령어
     * @description 키에 문자열 값을 설정
     * @example
     * SET Player:123:Changes Disconnect
     */
    await this.redis.set(`${playerKey}:Changes`, 'Disconnect');
  }

  /**
   * Redis Pub/Sub 이벤트 구독 설정
   * @description 게임 진행에 필요한 여러 Redis 이벤트를 구독
   */
  async subscribeRedisEvent(server: Server): Promise<void> {
    /**
     * Redis CONFIG SET 명령어
     * @description Redis 설정을 변경
     * @example
     * CONFIG SET notify-keyspace-events KEhx
     * K: 키스페이스 이벤트
     * E: 키이벤트
     * h: 해시 이벤트
     * x: 만료 이벤트
     */
    this.redis.config('SET', 'notify-keyspace-events', 'KEhx');

    /**
     * Redis PSUBSCRIBE 명령어
     * @description 패턴에 매칭되는 채널을 구독
     * @example
     * PSUBSCRIBE __keyspace@0__:Room:*
     */
    const subscriber = this.redis.duplicate();
    await subscriber.psubscribe('scoring:*');
    
    subscriber.on('pmessage', async (_pattern, channel, message) => {
      // 이벤트 처리 로직...
    });
  }
}