본문 바로가기

BackEnd

[Spring] 응애의 1:1 채팅 구현기(1) - 메시지 브로커를 선택하다

2025 산학프로젝트에서 사설 수리업체와 수리를 원하는 유저간 1:1 채팅을 구현할 일이 생겼다

요구사항은 다음과 같다.

수리가 필요한 유저와 수리센터간 일대일 챗팅에서 실시간으로 메시지 확인하는 것이 가능하도록 해야한다. 또한 기존 메시지 데이터가 읽음/안읽음 처리가 되어야하며 상대방이 보는 채팅방 또한 안읽음 읽음 으로 변경되어야 한다.

 

찾아보니 웹소켓만 사용해서 채팅을 구현할 수 있다고 하지만 다음 블로그를 읽어보고 메시지 브로커에 대해 관심이 생겼다.

https://binux.tistory.com/74

 

Message Broker - 왜 사용하는 것일까 ?

RabbitMQ, Kafka를 들어보기도 하고, 사내에서 사용하기도 하다보니 찾아보며 공부하는 것이 좋겠다 싶어 이렇게 정리하게 되었습니다. 뭐, 대충 어떠한 역할을 하는지는 알고 있었지만, 제일 중요

binux.tistory.com

한마디로 정리하자면 메시지 브로커 없이는 송신 및 수신 서버가 모두 다 정상적으로 작동하는 상태여야만 메시지 전송이 정상적으로 동작하게 된다. 그러나 브로커를 도입하면 송신자는 브로커에 메시지를 보내면 되고 수신자는 브로커로부터 메시지를 받아가기만 하면 되므로 서버 수가 유동적으로 변해도 일관된 메시지 처리가 가능하다.

이렇게만 보면 오!! 바로 도입하자!! 가 될 수 있는데

우리 서비스랑 그게 맞을까? 라는 고민을 엄청했다

 

1. 'FIXI' 서비스는 단일 서버

-> 단일 서버가 죽으면 어차피 전체 기능이 중단되므로 메시지 유실만 따로 막는 건 의미가 덜함

2. 챗팅 기록을 db에 저장할 예정

-> 서버가 일시적으로 장애가 나더라도 db로 복구 가능.. 

 

그러나 웹소켓이 메시지 수신, 전송, 저장 모두 담당하지 않고 메시지를 브로커에만 전달하고 응답과 저장은 백그라운드에서 비동기적으로 처리하도록 하고자 했다. 또한 알림 기능을 도입하자는 말도 있었기에 여러 consumer를 구성할 가능성도 생각하면 브로커를 사용하는 게 더 나을거라는 판단을 했다. 

 

방법1: Simple Broker (스프링부트 내장 메시지 브로커, STOMP 기반)

일반 웹소켓만 사용한 통신은 발신자와 수신자를 spring단에서 직접 관리 하지만 STOMP는 pub/sub기반으로 추가적으로 코드 작업할 필요 없이 MessagingMapping 같은 어노테이션을 사용해서 메시지 발행 시 엔드포인트만 조정해줌으로써 메시지 송수신 처리 명확하게 정의 가능

하지만 Nginx를 사용한 무중단 배포를 구현하려 했을때 Spring boot의 내장 브로커는 배포 과정에서의 메시지 연속성 보장에 한계 존재, 단일 서버 환경에서는 잘 작동하지만 무중단 배포 과정에서 메시지의 일관성을 보장하기 어렵다(인메모리 형식으로 갑작스럽게 서버가 Down되면, 내부의 데이터들을 모두 유실) 이외에도 spring boot서버 내에서 함께 처리하기 때문에 서버의 부담도 커지고 메시지 큐를 모니터링하기 어렵다

 

방법2: 외부 메시지 브로커(RabbitMQ)

외부 메시지 브로커를 사용하여 spring 서버의 부담을 줄이고 ui상으로 모니터링 가능하도록 설정. 또한 신뢰성과 일관성을 유지한다.

 

https://velog.io/@pjm2571/Rabbit-MQ-STOMP%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-11-%EC%B1%84%ED%8C%85

 

Rabbit MQ + STOMP를 이용한 1:1 채팅

차자바 프로젝트에서 1:1 채팅 서비스를 개발하면서, 단순한 웹소켓을 넘어서 보다 안정적이고 효율적인 실시간 메시징 시스템을 구축하고자 했습니다. 처음에는 STOMP(WebSocket)만을 사용하여 채

velog.io

RabbitMQ를 어떻게 활용할지는 위 블로그를 참고하여 설계하였다

 

STOMP란 Simple Text Oriented Messaging Protocol로 TCP 또는 웹소켓 같은 양방향 네트워크 프로토콜 기반으로 동작한다. pub/sub구조로 동작하여 특정 사용자 또는 특정 그룹에게 메시지를 전달하는 기능을 쉽게 구현할 수 있다!!

따라서 아래와 같은 구조로 설계하였다

 

[사용자 A]
↓
STOMP /app/chat.send
↓
Spring Controller
↓
RabbitMQ (chat.user.B)
↓
Listener (ChatMessageReceiver)
↓
SimpMessagingTemplate
↓
[사용자 B의 WebSocket]