📗 BOOK/가상 면접 사례로 배우는 대규모 시스템 설계 기초 정리

단일 메세지 모델 설계 하기(kafka + STOMP + Cassandra)

미미누 2024. 1. 3. 00:03

현재 스케일 아웃이 용이한 채팅 서버 설계를 위해서 스터디를 진행중이다. 단일 메시지 모델 설계를 위해 어떤 DB를 쓰고, 어떤 스택을 사용할 지 고민한 흔적을 소개해보려고 한다.

 

Reference

Best database for a chat application?

채팅을 위한 Message Queue 선택과 DB 선택

Kafka, Redis, Web Socket, Stomp 를 활용한 채팅 서버 회고

[혼자왔니] 채팅 서버 구현을 통해 알아본 redis와 kafka의 차이점

Redis, RabbitMQ 차이점을 알아보자

[WebSocket] Spring Boot + STOMP + Redis Pub/Sub 이용한 채팅 서버 구현

Spring STOMP

Spring Boot Web Chatting : 스프링 부트로 실시간 채팅 만들기(1) stomp, socketjs, websocket

[Spring] Gradle 프로젝트 생성 및 실행 (Intellij)

STOMP란?


STOMP?

  • WebSocket 프로토콜만 사용해서 채팅 서버만 구현한다면, 메시지 포멧 형식이나 통신 과정을 일일이 관리해야 하는 번거로움 → 메시지 처리에 최적화하기 위해 STOMP 사용
  • STOMP는 메시지 송수신을 효율적으로 하기 위해 나온 프로토콜, WebSocket이 하위 프로토콜, STOMP가 상위 프로토콜

장점

  • STOMP는 pub/sub 기반 동작하기 때문에, 메시지 송/수신에 대한 처리를 명확하게 정의 가능
  • @MessagingMapping 어노테이션을 사용하여, 메시지 발행시 엔드포인트만 조정하여 쉽게 메시지 발/수신 가능
  • 메시지 형식 정의 가능 → 클라이언트 서버간 통신에서 일관성 유지
  • 메시지 브로커를 사용할 수 있어, 메시지 전송 시 중계 역할을 하는 서버를 따로 둘 수 있음.
  • WebSocket 기반으로 각 connection마다, WebSocketHandler를 구현하는 것보다, @Controller 된 객체를 이용해 조직적 관리 가능
  • 메세지는 STOMP의 "destination" 헤더를 기반으로 @Controller 객체의 @MethodMapping 메서드로 라우팅 된다.
  • 메시지 보호 위해 Spring Security 사용 가능

STOMP Frame

COMMAND
header1:value1
header2:value2

Body^@
  • COMMAND: 메시지 타입을 나타내는 문자열 (SEND, SUBSCRIBE, UNSUBSCRIBE)
  • header1, header2 : 추가 정보를 제공하는 헤더(destination 헤더는 이 헤더로 메시지를 보내거나 구독 할 수 있다.)
  • Payload(Body): 메시지 내용(끝은 NULL 문자로 설정)

Sub/pub 개념

  • 채팅방 생성: pub/sub 구현을 위한 Topic 생성 → 채팅방 명 생성
  • 채팅방 입장: Topic 구독(sub) → 해당 채팅방을 웹 소켓이 연결되어 있는 동안 구독함. 새로운 채팅이 송신(pub) 되면, 이를 수신(sub) 할 수 있음.
  • 채팅방 메시지 수신: 해당 Topic로 메시지 송신(pub) → 해당 채팅방으로 메시지 송신한다.


인 메모리 브로커의 단점

  • 확장성: 단일 서버 환경에서 작동하며, 메시지를 메모리에 저장하고 관리 → 세션 수 제한
  • 결함 허용성: 장애 발생시 메시지 유실 가능성 높음
  • 모니터링: 모니터링 하는 것이 불편

외부 메시지 브로커가 필요한 이유

  • 확장성: RabbitMQ, 메시지 브로커는 클러스터링 및 분산 아키텍처 지원하여 세션수 확장 가능. 세션 수용량 한계 극복
  • 결함 허용성: 메시지를 디스크에 저장하고, 필요한 경우 재전송 → 안전성 및 신뢰성
  • 모니터링: RabbitMQ 관리자 도구를 통해 메시지 큐의 상태를 모니터링 하고, 웹 기반 인터페이스를 통해 세션 정보, 메시지 전송 및 수신 기록 확인 가능
  • 느슨한 결합 (Publisher는 메시지를 발신할 때 다른 서비스들에 대해 알 필요가 전혀 없음, scale-out 용이)

외부 메시지 브로커 어떤 것을 선택할까?

Redis & Kafka

인메모리 데이터베이스 / 컴퓨터 메모리를 이용한(in-memory) Cache 서버

Key-Value를 이용해 Celery가 처리할 작업을 Celery에 보낸 후 Cache 에서 해당 Key를 제거하는 방식으로 작동한다.

  • Redis는 데이터 검색을 위해 Cache를 가져다 쓴다는 점에서 속도가 빠르다.
  • 매우 빠른 서비스 및 메모리 내 기능을 제공하기 때문에 지속성이 중요하지 않고 약간의 손실을 견딜 수있는 짧은 보존 메시지에 적합하다.
  • 큰 메시지를 처리 할 때는 대기 시간이 오래 걸린다.

RabbitMQ

메시지 브로커

  • 메시지를 다른 대기열로 보낼 수있는 라우팅 시스템을 갖추고 있다.
  • 우선 순위가 높은 메시지를 먼저 사용하기 위해 작업자가 사용할 수 있는 메시지의 우선 순위를 지원한다.
  • 메시지 브로커로서 Redis와 비교할 때 훨씬 더 다양한 기능을 제공한다.
  • 크고 복잡한 메시지에 적합하다.

Kafka Redis

브로커 종류 이벤트 브로커(삭제 X)
속도 redis보다 느림(디스크 기반)
topic topic생성이 필요
삭제 작업 기본적으로 데이터가 삭제되지않고 따로 처리를 해야함
thread multi
scale-out 가능( zookeeper 필요)
무거운 정도 무거움
순서보장 O

설계

채팅 서비스의 고도화

  • 서버 재시작 할때마다 채팅방 정보들 리셋 → 인메모리 보다, 빠른 읽기가 가능한 redis 사용
  • 채팅 서버가 여러대일 때 서버간 채팅 방 공유 불가능 → 단순히 webSocket과 STOMP pub/sub를 이용하여 구현할 시, pub/sub이 발생한 서버 내에서만 메시지 주고 받는 것이 가능. (redis, rabbitMQ 등 별도의 메시지 브로커를 두어야 함)

요구 사항:

  • WebSocket을 활용한 실시간 채팅 구현
  • Stomp를 활용한 채팅 고도화 → pub/sub를 활용한 메시지 발신/수신
  • Redis 사용

동작 과정

  1. 채팅방 생성 → redis 저장소에 채팅방 정보 저장
  2. 채팅방 입장시 → web Socket 연결 수행, 해당 채팅방에 대한 subscribe를 수행
  3. 해당 채팅방에 message request → message를 받아서 message queue(redis)에 publish
  4. message queue(redis)가 websocket 구독자에게 메시지 전달

  • redis 사용 시 → message가 publish 되면 해당 channel을 구독하고 있는 모든 subscriber에게 메시지를 보내주게 된다. → 각기 다른 채팅 서버를 통해 접속했더라도 같은 채팅방에서 서로 대화 가능
  • REDIS 서버가 죽어버리면 안에 있는 내용이 위험하고 채팅방의 채팅이 많아질 경우 채팅의 순서 보장 및 유실 가능성이 커진다.

redis → 라이브 다중 채팅

kafka → 1:1 채팅 프로그램, 순서보장 중요하며 redis만큼 빠르지 않아도 됨

 

Cassandra vs MongoDB

  • Cassandra 노드가 추가될수록 MonogoDB 보다 훨씬 나은 선형적인 성능 향상을 보인다.
  • 다중 Index가 필요한 구조라면 MongoDB를 선택하고, 데이터 항목 변경이 많고 unique access가 많은 경우라면 Cassandra가 적합
  • noSQL이라 Read/write 속도가 빠름

 

- 결론: 웹 소켓 상단에 있는 STOMP를 사용하되, 메시지 브로커로는 Kafka를 사용하고, 빠른 읽기/쓰기가 가능한 Nosql 저장소인 Cassandra를 사용해보고자 한다!