Skip to content

우리가 Redis를 사용한 방식

Geon Seok, Song edited this page Dec 4, 2024 · 1 revision

📍 개요

부하 분산

  • 구현 초기부터 부하 분산을 위해 서버를 여러 대 사용할 수 있는 구조로 하기로 결정
  • 해당 과정에서 게임 데이터를 Redis에 저장하여 여러 서버에서 활용하도록 결정
  • 또, Redis의 Keyspace NotificationsPub/Sub을 활용하여 기능을 구현하기로 결정

❓ Why Redis

게임 세션 데이터 저장 및 동기화

  • 세션 동기화의 필요성
    • 퀴즈 게임을 구현하기 위해 게임 세션 데이터 저장이 필요
    • 같은 게임방에 있어도 서로 다른 서버에서의 처리를 가능하게 하는 구현을 시도
    • 여러 서버에서 같은 게임 처리를 해야 하기에 해당 세션 데이터가 모든 서버에서 접근 가능해야 함
  • Sticky Session, Session Clustering의 단점들을 Redis를 통해 극복 가능
    • Sticky Session
      • 게임방 마다 서버를 지정해줄 필요
      • 구현과 묶어서 로드 밸런싱을 진행해야 하는데, 쉽지 않음
    • Session Clustering
      • 서버가 늘어날 수록 네트워크 요청이 늘어남
    • Redis를 하나의 세션 데이터베이스로 관리함으로 위의 단점들을 극복 가능

Pub/Sub 기능, Keyspace Notifications 기능

  • 여러 서버에서 게임 기능을 구현하는 중, 각 서버 사이에 정보 전달들이 필요 (채팅 등)

    → Pub/Sub 기능 사용

  • 게임 진행 타이머를 관리해야 하는데, 어떤 서버에서 진행해야 할 지 정하기 애매함

    → Keyspace Notifications 기능 사용 (Expiration)

학습

  • 여러 실제 서비스에서 Redis를 사용하는 경우 다수
  • Redis를 학습하고, 기능을 이용해보는 경험

🛖 Redis 데이터 구조

erDiagram
    Room {
        string host "host_id"
        string status "0"
        string title "게임 제목"
        string gameMode "모드1"
        string maxPlayerCount "4"
        string isPublicGame "1"
        string isWaiting "0"
        string quizSetId "4"
        string quizCount "10"
        datetime lastAcitivityAt
    }
    RoomPlayers {
        set members "playerId1, playerId2, ..."
    }
    Player {
        string playerName "player_nickname"
        string positionX "0.3"
        string positionY "0.2"
        boolean disconnected "0"
        string gameId "123456"
        boolean isAnswerCorrect "0"
    }
    Quiz {
        string quiz "퀴즈 내용"
        string answer "1"
        string limitTime "10"
        string choiceCount "4"
    }
    QuizChoices {
        string choice1 "보기1"
        string choice2 "보기2"
        string choice3 "보기3"
        string choice4 "보기4"
    }
    Leaderboard {
        sortedSet member "playerId"
        sortedSet score "score"
    }
    CurrentQuiz {
        string currentQuizId
    }
    Timer {
        string remainingTime "TTL 사용"
    }
    QuizSet {
        list quizIds "quizId, quizId, quizId..."
    }

    Room ||--o{ RoomPlayers : contains
    Room ||--o{ Quiz : has
    Quiz ||--o{ QuizChoices : has
    Room ||--o{ Leaderboard : has
    Room ||--o{ CurrentQuiz : tracks
    Room ||--o{ Timer : manages
    Room ||--o{ QuizSet : organizes
    Player ||--o{ Room : belongsTo

Loading
실제 구현 시 사용한 데이터 모델링
# 게임방 관련
Room:<gameId> → Hash
- host: "host_id"
- status: "0"
- title: "게임 제목"
- gameMode: "모드1"
- maxPlayerCount: "4"
- isPublicGame: "1"
- isWaiting: "0"
- quizSetId: "4"
- quizCount: "10" #랜덤 10문제     
- lastAcitivityAt: #고인 방 삭제하기 위함

**# 방 참가자 목록 (다중 서버일 때 브로드캐스팅 고려해서)**
Room:<gameId>:Players → Set #중복방지
- members: [playerId1, playerId2, ...]

# 플레이어 정보 (241113 'Room:<gameId>' 삭제. playerId만을 가지고 어떤 게임방에 있는지 쉽게 파악하기 위함)
Player:<playerId> → Hash
- playerName: "player_nickname" #241112 nickname에서 playerName으로 변경함 (용어 맞추기 위해)
- positionX: "0.3"
- positionY: "0.2"
- disconnected: "0"
- gameId: "123456"
- isAnswerCorrect: "0"

# 퀴즈셋 관련 (quizId는 DB quiz 테이블에 저장되어 있는 기본키)
Room:<gameId>:Quiz:<quizId> → Hash
- quiz: "퀴즈 내용"
- answer: "1"
- limitTime: "10"
- choiceCount: "4"

Room:<gameId>:Quiz:<quizId>:Choices → Hash
- 1: "보기1"
- 2: "보기2"
- 3: "보기3"
- 4: "보기4"

# 순위 리더보드
Room:<gameId>:Leaderboard → Sorted Set
- member: <playerId>
- score: <score>

# 현재 퀴즈 Id와 상태 저장
Room:<gameId>:CurrentQuiz -> String

# 타이머
Room:<gameId>:Timer -> String
(TTL 걸어둬서 타이머처럼 사용)
(NX 옵션 사용)

# 진행되어야 하는 퀴즈들 번호 순서대로 저장 
Room:<gameId>:QuizSet -> List
- quizId, quizId, quizId ...

👨🏻‍💻 Redis를 이용한 구현

Pub/Sub을 활용한 게임방 채팅 구현

flowchart TD
    style R fill:#ffeb3b,stroke:#fbc02d,stroke-width:2px,color:#000
    style S1 fill:#e3f2fd,stroke:#2196f3,stroke-width:2px,color:#000
    style S2 fill:#e3f2fd,stroke:#2196f3,stroke-width:2px,color:#000
    style P1 fill:#ffffff,stroke:#4caf50,stroke-width:2px,color:#000
    style P2 fill:#ffffff,stroke:#4caf50,stroke-width:2px,color:#000

    R[Redis]
    S1[게임 서버 1] -->|publish message| R
    R -->|메시지 수신| S1
    R -->|메시지 수신| S2[게임 서버 2]
    P1[플레이어 1] -->|채팅 메시지 입력| S1
    S1 -->|메시지 전달| P1
    S2 -->|메시지 전달| P2[플레이어 2]
    
    linkStyle 0,3 stroke:blue,stroke-width:2px
    linkStyle 1,2,4,5 stroke:red,stroke-width:2px

Loading
  1. 채팅 메시지 입력
    • 플레이어가 채팅 입력 시, 해당 메시지는 사용자가 접속한 서버로 전송.
  2. Redis에 Publish
    • 서버는 메시지를 chat:<gameId> 형태의 채널에 publish
  3. 모든 서버에서 메시지 수신
    • 모든 게임 서버는 chat:* 패턴으로 psubscribe 상태.
    • 특정 gameId 의 채널에서 메시지가 발행되면 수신.
  4. 플레이어들에게 메시지 전달
    • 메시지를 수신한 서버는 해당 게임방에 접속해 있는 플레이어들에게 메시지 전송.

EX, NX를 활용한 게임 타이머 및 진행

flowchart TD
    style Host fill:#e3f2fd,stroke:#2196f3,stroke-width:2px,color:#000
    style Redis fill:#ffeb3b,stroke:#fbc02d,stroke-width:2px,color:#000
    style ServerN fill:#e3f2fd,stroke:#2196f3,stroke-width:2px,color:#000
    style Client fill:#ffffff,stroke:#4caf50,stroke-width:2px,color:#000

    Host[호스트 서버] -->|"처음 타이머 데이터 설정 (EX)"| Redis
    Redis -->|"keyspace event 발생"| ServerN[전체 서버]
    ServerN -->|"다음 타이머 데이터 설정 (EX, NX)"| Redis
    ServerN -->|"게임 진행 메시지 전송"| Client[클라이언트들]

    linkStyle 0,2 stroke:blue,stroke-width:2px
    linkStyle 1,3 stroke:red,stroke-width:2px

Loading
  1. 처음 타이머 데이터 설정
    • 게임 시작 시, 호스트가 접속한 서버에서 Redis에 타이머 데이터를 EX 옵션과 함께 설정.
      • EX : 데이터 만료 시간 설정.
      • NX : 데이터가 없을 경우에만 설정.
  2. Keyspace Notifications 활용
    • Redis는 타이머 데이터 만료 시 keyspace event 를 발생시킴.
    • 모든 게임 서버는 해당 이벤트를 구독.
  3. 게임 진행 동기화
    • 이벤트가 발생하면, 모든 서버는 다음 단계로 진행하도록 클라이언트에 메시지 전송.
    • 다음 단계 타이머 데이터를 EXNX 옵션으로 재설정.
    • NX 옵션으로 인해 이미 설정된 경우는 새로 설정되지 않음.
  4. 반복 진행
    • 게임 종료 시까지 위 과정을 반복.

이 방식의 장점

  • Pub/Sub 방식으로 채팅 메시지를 간단하고 효율적으로 브로드캐스트 가능.
  • Redis의 EX와 NX 옵션 및 Keyspace Notifications를 활용하여 서버 간 게임 진행 동기화 가능.
  • 추가적인 게임 진행 서버를 구현할 필요 없이 간단한 Redis 설정으로 처리 가능.

⭐️ 결론

요약

  • 게임 세션 저장 및 동기화, Pub/Sub 기능, Keyspace Notifications 기능 등을 사용하기 위해 Redis를 프로젝트에 도입
  • 구현 전 Redis 데이터 구조를 미리 작성해두고, 구현 시작
  • Pub/Sub을 이용해 게임방 내 채팅 기능을 구현
  • Keyspace Notifications 기능을 이용해 게임 진행 타이머 기능을 구현

레퍼런스

[Security] Sticky Session & Session Clustering

Redis Pub/Sub

SET

Redis Keyspace Notifications에 대해 알아보자

Clone this wiki locally