서론
저희 팀은 기술적인 도전으로 MSA 아키텍처를 도입하기로 했습니다.
MSA의 장점으로는 서비스에 대한 scale out이 용이하다입니다.
그렇다면 MSA 도입 시 채팅 서비스도 scale out이 용이할까요?
채팅 서비스는 일반적인 HTTP 통신을 사용하는 stateless 서비스와 달리, 웹소켓을 사용해 클라이언트와 연결된 stateful한 특성 때문에 scale out이 쉽지 않았습니다.
이번 글에서는 MSA 아키텍처 내 scale out이 용이하지 않은 채팅 서비스에 대한 문제를 해결하는 과정을 적어보겠습니다.
채팅 서비스 scalue out시 문제점
왜 stateful 한다면 scalue out이 용이하지 않을까요?
하나의 채팅 서비스에 두 명의 유저가 웹소켓으로 연결되어 있고
1번 유저와 2번 유저 둘 다 11번 채팅방에 입장해 서로 채팅을 하고 있다고 하겠습니다.
이때 새로운 채팅 서비스 2가 스케일 아웃이 되고 유저 3번이 채팅 서비스 2에 웹소켓으로 연결된다고 하겠습니다.
유저 3은 11번 채팅방에 입장하고 채팅 메시지를 전송합니다.
유저 3이 보낸 채팅 메시지는 유저 1과 유저 2에게 전송이 될까요?
당연히 되지 않습니다.
유저 3이 보낸 채팅 메시지는 채팅 서비스 2에만 전송이 될 뿐 채팅 서비스 1에는 존재하지 않습니다.
즉 채팅 서비스 간 채팅 메시지 동기화가 필요한 상황입니다.
이처럼 유저들이 특정 채팅 서비스에 웹소켓으로 연결이 되어 있는 stateful 한 상태에서는 채팅 서비스 간 채팅 메시지 동기화 문제로 인해 채팅 서비스의 scalue out이 용이하지 않습니다.
그렇다면 채팅 서비스가 scalue out 한 경우 채팅 서비스 간 채팅 메시지 동기화는 어떻게 해결할 수 있을까요?
단순하게 드는 해결법은 채팅 메시지를 받은 채팅 서비스가 직접 다른 채팅 서비스에게 메시지를 전송하는 것입니다.
하지만 이 방법은 채팅 서비스가 다른 채팅 서비스에 직접 전송하므로 채팅 서비스 간 결합도가 높아집니다.
또한 채팅 서비스는 클라이언트와 웹소켓으로 지속적으로 연결이 되어 있는 상태라 부하가 심한 상태인데 여기서 받은 메시지를 직접 다른 서비스에게 전송한다면 굉장한 부하가 올 거라고 예상이 됩니다.
해결해야 하는 문제는 2가지인 거 같습니다.
1. 하나의 채팅 서비스에서 발생한 메시지를 다른 채팅 서비스로 전달할 방법
2. 채팅 서비스 간 직접적인 결합을 피하면서 부하를 줄일 방법
위 문제를 해결하기 위해 메시지 브로커를 도입하여 문제를 해결했습니다.
메시지 브로커 도입
우선 메시지 브로커에 대해 간단하게 알아보겠습니다.
메시지 브로커 (message broker) 란?
- 안정적인 비동기 방식으로 메시지를 교환하여 분산된 시스템 혹은 애플리케이션 간의 통신을 돕는다.
- 생산자와 소비자 간 중개자 역할을 하여 두 시스템이 완전히 독립적으로 작동할 수 있도록 돕는다.
- 컴포넌트 간 느슨한 결합 (loose coupling)을 돕는다.
- 이 구조는 확장성을 증대시켜 주고 장애가 미치는 여파를 줄일 수 있습니다.
- 메시지 큐, 토픽과 같은 데이터구조를 통해 메시지를 임시로 저장해 두었다가 적절한 수신자에게 보내줍니다.
메시지 브로커를 도입한다면 채팅 서비스는 직접 다른 채팅 서비스로 메시지를 보내는 게 아니라 메시지 브로커에 메시지를 보내고 다른 채팅 서비스들은 메시지 브로커로부터 메시지를 받아 옵니다.
이러한 방식을 통해 채팅 서비스 간 메시지 동기화를 해결할 수 있고 채팅 서비스에서 직접 보낼 경우 발생하는 부하와 높은 결합도를 해결 할 수 있습니다.
카프카 도입
메시지 브로커로 redis pub sub과 래빗엠큐 카프카에 대해 고민을 했고 아래와 같은 이유로 카프카를 선택했습니다.
고가용성: 아파치 카프카는 다수의 브로커로 구성되어 있어, 하나의 브로커가 다운되더라도 다른 브로커가 대신 처리할 수 있어, 고가용성을 보장합니다.
확장성: 아파치 카프카는 수평적으로 확장 가능합니다. 즉, 브로커를 추가하면서 시스템의 처리량을 확장할 수 있습니다.
FitTrip 채팅 아키텍처
현재 FitTirp의 채팅 아키텍처입니다.
카프카에 채팅 메시지에 대한 토픽을 만들고 이 토픽에 대한 producer와 consumer를 하나의 채팅 서버에 구현했습니다. 이를 통해 카프카를 메시지 브로커로 사용하여 다중 채팅 서버 간 메시지 동기화를 해결했습니다.
이때 메시지의 처리 순서를 보장하기 위해 토픽의 파티션 개수는 한 개로 설정했고 서버별로 컨슈머 그룹을 달리하여 토픽 안의 하나의 파티션을 큐처럼 사용했습니다.
정리
정리하면 웹소켓으로 클라와 연결되어 있어 채팅 서비스의 scale out이 쉽지 않았고 이러한 문제를 해결하기 위해
메시지 브로커인 카프카를 도입해서 채팅 서비스 간 메시지 동기화를 문제를 해결했습니다.
아쉬운 점
아쉬운 부분도 존재합니다.
첫 번째는 채팅 서비스가 카프카로부터 데이터를 가져올 때 채팅 메시지의 순서를 보장하기 위해 토픽에 있는 파티션이 한 개로 고정되어 있습니다. 그러다 보니 파티션을 여러 개 둬서 빠른 병렬처리가 가능한 카프카의 장점을 없애는 부분이 아쉬웠습니다. (물론 카프카가 대용량 데이터의 분산 처리에 특화되어 있어 파티션을 하나만 둬도 어지간한 규모에서는 감당이 가능하다고 합니다.)
두 번째는 채팅 토픽이 하나만 존재하고 모든 채팅 서비스가 해당 토픽에 채팅을 보내는 상황이라 특정 채팅 서비스는 토픽에 있는 채팅을 가져올 필요가 없는데 가져오게 되는 불필요한 트래픽이 발생하게 됩니다.
얻음 점
이번 프로젝트는 웹소켓과 카프카를 처음 사용해보는 경험이었고, 여러 시행착오를 겪으면서 많은 것을 배울 수 있었습니다. 특히 문제 해결 과정에서 얻은 기술적 지식과 MSA 구조에 대한 이해는 앞으로의 프로젝트에서도 큰 도움이 될 것입니다.
'프로젝트 > FitTrip' 카테고리의 다른 글
개발 기록 - MongoDB 트랜잭션 도입 (0) | 2024.06.29 |
---|---|
개발 기록 - 채팅 서비스 몽고DB 데이터 모델링 (0) | 2024.06.28 |
개발 기록 - 개발 언어 및 기반 기술 조사 (0) | 2024.06.27 |
개발 기록 - 프로젝트 공통 작업 설정 (0) | 2024.06.27 |
개발 기록 - MSA에서의 반정규화 데이터 동기 처리를 위해 EDA 설계 (0) | 2024.06.26 |