# 라이브러리-마이 룸 서버 ### 사용 기술 - 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 라이브러리를 사용했다면 다른 방법을 통해 인증이 적절하게 이루어지겠지만, 현재는 소켓 연결 후 인증 메세지를 따로 보내서 합니다. - 이 방식은 소켓 연결 자체는 검증하지 않는다는 것이기 때문에 공격에 취약한 단점이 있습니다. - 이 또한 설계상의 오류라고 생각하고, 앞으로 개선해야겠다고 생각했습니다.