---
# System prepended metadata

title: 라이브러리-마이 룸 서버

---

# 라이브러리-마이 룸 서버
### 사용 기술
- Spring Boot 2.6.3
- RabbitMQ 3.9.13
  - Erlang 24.2.1
  - RabbitMQ의 Stream Queue를 사용하기 위해 3.9.x 의 RabbitMQ 필요
- WebSocket
  - 실시간 양방향 이벤트 전달
- Redisson 3.16.8
  - 분산 락을 이용한 Redis 동시성 관리
### Infra
- 로드밸런싱
  - Spring Cloud Netflix Eureka 서버에 등록하고 Spring Cloud Gateway에서 HandShaking 과정 중 라운드 로빈 방식으로 로드밸런싱
- DB
  - Redis: 방 정보(유저 위치, 맵), 유저 세션 정보
  - MySQL: 유저 정보, 라이브러리-게임 목록 정보

### 기능 종류
- 입장
- 이동
- 맵 변경
  - 게임 오브젝트 건설
- 퇴장
- [관련 정의 JSON 파일](https://github.com/STOVE-Milk/steam-clone/blob/develop/src/common/robby_message_protocol.json)

### 아키텍처
![](https://i.imgur.com/2tN3UZy.png)
![](https://i.imgur.com/H1Bvs88.png)



### 입장
- 입장 과정
  - 세션 연결 후 바로 인증 토큰과 함께 입장
    - 유저 검증
  - 같은 방에 중복으로 접속했는지 Redis 유저, 방, 세션 정보 확인
    - 같은 방에 중복 접속할 경우 같은 유저가 두명이 될 수 있음
    - 중복 접속일 경우 기존 세션 끊기 메세지 발행
      - 서버 내 기존 정보 삭제
      - 기존 세션 끊기
  - 서버 내 유저 상태정보 등록
  - Redis에 유저, 방, 세션 정보 기록
  - Redis의 방 데이터에 유저 상태정보 추가
  - 입장 메세지 발행

### 이동
- 이동 과정
  - 유저가 `w`,`a`,`s`,`d` 키를 눌러 이동
  - 서버 내 유저 위치정보 수정
  - Redis 내 유저 위치정보 수정
  - 이동 메세지 발행

### 맵 변경
- 변경 과정
  - 웹에서 유저가 게임 오브젝트를 특정 좌표에 위치시키고 해당 정보를 전송
  - 유저가 소유한 방인지 검증
  - MySQL 내 맵 정보 수정 및 서버 내 등록된 유저 위치 초기화
  - Redis 내 맵 정보 수정 및 모든 유저 위치 초기화
    - 건물에 Block될 수 있기 때문
  - 타 서버 초기화 메세지 및 동기화 메세지 발행

### 퇴장
- 퇴장 과정
  - 퇴장 이벤트 전송 or 소켓 비정상 끊김
  - 서버 내 유저 정보 삭제
  - Redis 내 유저 정보 삭제
  - 퇴장 메세지 발행

### 기술 선택 이유
1. `웹 소켓` + `RabbitMQ`
   - 우리의 서비스는 유저가 입장, 이동, 퇴장하면서 여러 유저의 정보를 실시간으로 동기화시키기 위해서는 **응답속도**가 매우 빨라야겠다고 생각했습니다.
   - `웹 소켓`
     - 위와 같은 의미에서 라이브러리를 사용하는 것이 속도의 저하로 이어질 수 있다고 생각했습니다.
   - `RabbitMQ`
     - Kafka와 RabbitMQ의 선택에 있어서 고민이 많았습니다.
     - ![](https://i.imgur.com/xB51Bk7.png)
     - Kafka는 엄청난 처리량과 그럼에도 불구하고 준수한 응답 속도를 보장합니다. 하지만 우리가 생각하기에 처리량이 적어도 **훨씬 빠른 응답속도**를 보장하고 메세지 큐의 특징으로 **신뢰성**까지 보장하는 RabbitMQ가 더욱 우리 서비스에 맞다고 생각했습니다.

### 겪은 문제들
- [Link]()
### 느낀점
- **웹 소켓**: `실력`, `안정성`, `가용성`
  - 구현을 하면서 느낀 점은 제가 직접 작성한 코드로 인해 오히려 메모리 누수와 속도 저하가 일어난다고 느끼게 되었습니다.
  - 추가로 서비스라는 것은 유저에게 안정적으로 정확한 서비스를 하는 것이 더 중요하다고 생각되는데, 순수한 기술을 사용하면서 사용성을 보장하지 못하는 경우가 발생했습니다.
  - 여러 측면에서 봤을 때, 전문가가 만들어놓은 라이브러리의 필요성을 매우 느꼈고, 각 기능들의 구현 이유를 알게 됐습니다.
- **RabbitMQ**: `가용성`, `현실 고려`
  - Jmeter를 이용해 수십만개의 소켓 메세지를 보내봤습니다.
  - 메세지 큐는 금방 가득찼고, 서비스의 엄청난 속도 저하로 이어졌습니다.
  - 유저 수만명이 동시에 많이 움직인다면 이는 전체적인 속도 저하로 이어지고 서비스 불가능 상태가 될 것입니다.
  - 설계를 할 때, 유저가 몇 명일지, 유저가 초당 어느정도의 이벤트를 발생시킬지 고려를 안해서 발생한 문제라고 생각합니다.
  - 앞으로 설계 진행 시 다양한 시각으로 문제를 예측할 필요성을 느꼈습니다.
- **동시성관리**: `MSA`, `큰 규모`, `목적`
  - 현재 설계에 있어서 상태관리 서버가 존재하지 않습니다.
  - 상태관리 서버는 상태만 저장한다고 생각하여, 단순하게 Redis에 상태 정보를 저장하여 공유하면 된다고 생각했습니다.
  - 하지만 여러 서버가 동시에 Redis에 접근한다는 것을 고려하지 못했고, 이는 동시성 문제로 이어졌습니다.
  - 분산 락 기능을 통해 해결했지만, 기본적으로 여러 서버가 동시에 요청한다는 것과 상태 관리 서버의 목적을 제대로 고려하지 못했던 것은 아쉽게 느껴집니다.
  - 앞으로는 MSA에서의 각 서버나 기술들의 목적을 단편화하여 보지 않고, 하나하나 따질 필요가 있다고 생각했습니다. 
- **로드 밸런싱**: `알고리즘` `그를 위한 기술`
  - 웹 소켓 연결 시 API Gateway를 거쳐서 Handshaking을 하며 그 과정에서 로드밸런싱을 진행합니다.
  - API Gateway는 라운드 로빈 방식의 로드밸런싱밖에 지원하지 않고, 상태 관리 서버의 부재에 따라 따로 최소 연결 방식과 같은 알고리즘을 구현할 수 없었습니다.
  - 이는 서비스를 할 때, 어느 한 서버에만 오래 유저가 연결하고 있을 경우 트래픽이 점점 한쪽에만 커져 문제가 발생할 수 있습니다.
  - 앞으로 웹 소켓 연결과 같이 특수한 경우 로드 밸런싱을 어떻게 할 것인지 고려할 필요성을 알게됐습니다.
- **웹 소켓 인증**: `웹 소켓 특성`, `라이브러리의 장점`
  - 웹 소켓은 API Gateway를 거쳐서 Handshaking을 하지만, Upgrade 과정에서 인증에 필요한 헤더를 설정할 수 없었습니다.
  - SockJS나 STOMP 라이브러리를 사용했다면 다른 방법을 통해 인증이 적절하게 이루어지겠지만, 현재는 소켓 연결 후 인증 메세지를 따로 보내서 합니다.
  - 이 방식은 소켓 연결 자체는 검증하지 않는다는 것이기 때문에 공격에 취약한 단점이 있습니다.
  - 이 또한 설계상의 오류라고 생각하고, 앞으로 개선해야겠다고 생각했습니다.