Skip to content

JiNookk/mafia_server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mafia Game Server

8인 실시간 마피아 게임을 위한 완전 비동기(Fully Reactive) 백엔드 서버

WebFlux + R2DBC + Redis로 구현한 논블로킹 게임 서버. 동시성 제어, 실시간 통신, 상태 기반 게임 로직을 직접 설계하고 구현했습니다.


Why This Project?

마피아 게임은 단순해 보이지만, 서버 관점에서는 까다로운 문제들이 있습니다:

  • 동시성: 8명이 동시에 투표하면 race condition 발생
  • 실시간성: 밤/낮/투표 페이즈가 시간 제한 내에 자동 전환되어야 함
  • 상태 일관성: 분산 환경에서도 게임 상태가 정확해야 함

이 프로젝트는 이런 문제들을 Redis 분산 락, 이벤트 기반 스케줄링, 리액티브 스트림으로 해결합니다.


Key Technical Challenges

1. 동시 투표 시 Race Condition 해결

8명이 동시에 투표하면 집계가 꼬일 수 있습니다. Redis SETNX 기반 분산 락으로 해결:

// RedisLockService.java
public Mono<String> acquireLock(String lockKey) {
    return redisTemplate.opsForValue()
        .setIfAbsent(fullKey, lockToken, LOCK_TTL)  // atomic operation
        .retryWhen(Retry.backoff(MAX_RETRY_ATTEMPTS, RETRY_DELAY));
}
  • TTL 10초: 데드락 방지
  • Lock Token: 소유권 검증으로 다른 프로세스의 락 해제 방지
  • Exponential Backoff: 재시도 시 부하 분산

2. 시간 제한 페이즈 자동 전환

밤(30초) → 낮(30초) → 투표(10초) → 변론(10초) → 결과(10초) 순환:

// GameSchedulerService - Spring Event 기반
@EventListener
public void onPhaseChanged(PhaseChangedEvent event) {
    int duration = event.getPhaseDurationSeconds();
    Mono.delay(Duration.ofSeconds(duration))
        .then(gameService.nextPhase(gameId))
        .subscribe();  // non-blocking
}

스케줄러가 블로킹하지 않고, 이벤트 발행으로 다음 페이즈를 예약합니다.

3. 완전 논블로킹 파이프라인

DB부터 WebSocket까지 전 구간 리액티브:

WebSocket Request
    ↓ (Reactive)
GameService (Mono/Flux)
    ↓ (R2DBC - non-blocking)
MySQL / Redis
    ↓ (Reactive)
Redis Pub/Sub → WebSocket Broadcast

단일 스레드에서도 높은 동시 처리량을 보장합니다.


Tech Stack

Layer Technology
Runtime Java 21, Spring Boot 3.5, WebFlux
Database MySQL 8 (R2DBC), AWS RDS
Cache & Lock Redis (AWS ElastiCache Valkey)
Realtime WebSocket, Redis Pub/Sub
Infra Docker, AWS ECR, EC2, Watchtower

Game Flow

NIGHT (30s)                    DAY (30s)                 VOTE (10s)
 ├─ 마피아: 시민 지목            ├─ 토론                    ├─ 투표 (과반수)
 ├─ 의사: 치료 대상 지목         └─ 경찰 조사 결과 공유      └─ 최다 득표자 → 재판
 └─ 경찰: 조사 대상 지목
                                                              ↓
                              RESULT (10s)              DEFENSE (10s)
                               ├─ 찬/반 투표              └─ 최후 변론
                               └─ 과반 찬성 시 처형

승리 조건

  • 시민팀: 마피아 전원 처형
  • 마피아팀: 마피아 수 >= 시민 수

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Client                               │
│              (WebSocket + REST API)                         │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│                    Spring WebFlux                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   REST API  │  │  WebSocket  │  │  Event Publisher    │  │
│  │  Controller │  │   Handler   │  │  (Phase Scheduler)  │  │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬──────────┘  │
│         │                │                    │              │
│  ┌──────▼────────────────▼────────────────────▼──────────┐  │
│  │                  Service Layer                        │  │
│  │  GameService │ VoteCacheService │ RedisLockService    │  │
│  └──────────────────────┬────────────────────────────────┘  │
└─────────────────────────┼───────────────────────────────────┘
                          │
        ┌─────────────────┼─────────────────┐
        │                 │                 │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│   MySQL 8.x   │ │     Redis     │ │  Redis Pub/Sub │
│   (R2DBC)     │ │  Cache + Lock │ │   Broadcast    │
│               │ │               │ │                │
│ - users       │ │ - game state  │ │ - room:{id}    │
│ - games       │ │ - vote cache  │ │ - game:{id}    │
│ - actions     │ │ - dist lock   │ │                │
└───────────────┘ └───────────────┘ └───────────────┘

AWS Infrastructure

graph TB
    subgraph "CI/CD"
        DEV[Developer] -->|docker push| ECR[ECR]
        DEV -->|git push| GHA[GitHub Actions]
    end

    subgraph "AWS"
        ECR -->|pull| WT[Watchtower]
        WT -->|restart| APP[Spring Boot Container]
        APP -->|R2DBC| RDS[(RDS MySQL)]
        APP -->|Redis| CACHE[(ElastiCache)]

        TG[Health Check] -->|unhealthy| CW[CloudWatch]
        CW -->|trigger| LAMBDA[Lambda Auto-Recovery]
    end
Loading

자동화 포인트

  • Watchtower가 5분마다 ECR 폴링 → 새 이미지 감지 시 자동 배포
  • Health check 실패 시 Lambda가 EC2 자동 재부팅

Project Structure

src/main/java/com/jingwook/mafia_server/
├── config/          # WebSocket, Redis, R2DBC 설정
├── controllers/     # REST API 엔드포인트
├── handlers/        # WebSocket 메시지 핸들러
├── services/        # 비즈니스 로직 (GameService, RedisLockService)
├── domains/         # 도메인 모델 (순수 Java 로직)
├── entities/        # R2DBC 엔티티
├── events/          # Spring Event (PhaseChangedEvent 등)
└── enums/           # GamePhase, PlayerRole 등

Local Setup

# 1. 의존성 실행
docker-compose up -d

# 2. DB 초기화
mysql -h localhost -P 3307 -u root -p < init_database.sql

# 3. 실행
./gradlew bootRun

API 문서: http://localhost:8080/swagger-ui.html

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors