STOMP와 Pub/Sub구조

chanbyeong·2025년 3월 27일
post-thumbnail

WebSocket의 단점

1. 메시지 프로토콜의 부재

  • WebSocket은 단순히 텍스트나 바이너리 데이터를 주고받는 것만 지원한다.
  • 메시지를 구분하거나, 목적지를 지정하거나, 구독을 관리하는 등의 고급 기능이 없다
  • ex) "chat-room-1"에 메시지를 보내고 싶어도 별도의 규칙을 직접 정의해야 한다.

2. Session 및 사용자 관리의 어려움

  • WebSocket 연결은 단순히 소켓 연결일 뿐, 사용자 정보나 인증 상태를 관리해주지 않는다.
  • 클라이언트가 누구인지 구분하거나, 로그인 정보를 통해 권한을 확인하려면 별도 구현이 필요하다.

3. Pub/Sub 구조 직접 구현이 어려움

  • 채팅방처럼 특정 주제(Topic)에 대해 다수의 사용자와 메시지를 주고 받으려면, Pub/Sub 구조를 직접 구현해야 한다.
  • 메시지를 어떤 클라이언트가 구독하고 있는지, 어떤 채널로 전달해야 하는지 등 복잡한 로직이 필요하다.

4. 메시지 타입이나 상태 구분이 어려움

  • 단순 문자열로 메시지를 주고받기 때문에, 메시지의 유형(ex: 채팅, 알림, 시스템 메시지 등)이나 구조를 파악하기 힘들다.
  • 클라이언트/서버 모두에서 파싱 로직을 별도로 관리해야 하며, 실수하기 쉽다.

상위 프로토콜의 필요성

  • 이러한 WebSocket의 한계를 보완하기 위해 STOMP, SockJS와 같은 상위 메시징 프로토콜이 등장했다.
  • 이들은 WebSocket 위에 구조화된 메시징 방식, 세션 관리, 호환성 개선등 실질적인 기능을 추가로 제공한다.

STOMP와 SockJs

STOMP(Simple Text Oriented Messaging Protocol)

  • 웹소켓 위에서 동작하는 메시지 프로토콜
  • 메시징 시스템 간에 데이터를 교환하기 위한 간단하면서 유연한 텍스트 기반 프로토콜이다.

주요 기능

1. 구독 기반 메시징 지원

  • STOMP는 채팅방 같은 Pub/Sub 구조를 쉽게 구현할 수 있도록 /topic/roomId 같은 경로 기반 구독을 지원한다.

2. 메시지 목적지를 명확하게 지정 가능

  • 클라이언트가 /app/send, /topic/roomId 등으로 메시지를 보낼 수 있어 구조가 명확해진다.

3. 메시지 타입과 헤더 관리 지원

  • STOMP 메세지는 헤더와 바디로 구성되기 때문에, 메시지의 목적, 유형 등을 명시적으로 표현할 수 있다.

4. Spring과의 통합이 용이함

  • Spring에서는 @MessageMapping, @SendTo, SimpleMessagingTemplate등을 활용해 손쉽게 메시지를 송수신 할 수 있어 생산성이 높다.

5. Session 연결 및 인증과 연동 가능

  • Principl, HanshakeInterceptor 등을 통해 로그인 사용자 정보를 WebSocket 세션에 연동 할 수 있다.

SockJs

  • WebSocket이 동작하지 않는 환경에서도 동작할 수 있도록 도와주는 FallBack 라이브러리
  • WebSocket을 기본으로 사용하되, 브라우저가 지원하지 않으면 자동으로 Long Polling 등으로 대체됨
  • 클라이언트와 서버 간의 호환성 문제를 해결하는데 효과적

STOMP의 Pub/Sub 구조

Pub/Sub

Pub/Sub는 발행/구독 모델이라고 하며, 다음과 같은 구조로 이루어짐

  • Publisher: 메시지를 **특정 주제(Topic)에 발행(Publish)함
  • Subscriber: 특정 Topic을 구독하고 있다가, 해당 주제로 메시지가 오면 자동으로 수신받음

이 구조의 핵심은 발행자와 구독자가 서로를 알 필요가 없고, 모든 통신이 메시지 브로커를 통해 중개됨

Message Broker

메시지 브로커는 발행자와 구독자 사이에서 메시지를 중개하는 역할을 하는 컴포넌트, 쉽게 말해 전달 담당자 역할을 함

  • 주요 역할
    • 클라이언트로 부터 수신한 메시지를 해당 주제를 구독 중인 모든 사용자에게 전달
    • 비동기 메시징 처리: 발신자와 수신자가 동시에 연결되어 있을 필요 없음
    • 로드 분산 및 안전성 향상: 데규모 메시지를 안정적으로 처리 가능

Channel

  • Input Channel
    • Publisher가 메시지를 직접 Message Broker에 보내는 것이 아닌 Input Channel을 통해 보냄
    • 브로커에게 메시지를 넘기는 중간 다리
  • Output Channel
    • Message Broker가 처리한 메시지를 Subscriber에게 전달하는 출구
    • 브로커는 메시지를 어떤 주제를 구독하고 있는 구독자들에게 보냄

왜 중간에 채널을 따로 두지?

  • 역할 분리: 메시지 발행과 브로커 로직, 메시지 소비를 분리해서 관리함
  • 확장성: 채널을 기준으로 필터링, 로깅, 변환 등을 쉽게 끼워넣을 수 있음
  • 비동기 처리: Input/Ouput Channel이 있으면 메시지를 큐처렁 처리하거나, 지연을 줄일 수 있음
  • 유연한 구조: 채널을 통해 여러 Publisher <-> 여러 Subscriber 구성이 가능함

실제 Spring에서의 구현

// WebSocketConfig
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 클라이언트가 구독할 prefix 설정 (예: /topic)
        config.enableSimpleBroker("/topic");
        // 클라이언트가 메시지를 보낼 때 사용하는 prefix 설정 (예: /app)
        config.setApplicationDestinationPrefixes("/app");
    }
  • /topic의 경우 클라이언트가 직접 구독하는 경로로, 브로커가 그대로 메시지를 전달해주는 역할을 한다.
  • /app은 클라이언트가 서버로 메시지를 보내는 경로이며, 서버는 해당 메시지를 받아 로직을 처리하거나 가공 한 뒤 필요시 /topic을 통해 다시 클라이언트에게 메시지를 보낼 수 있다.
// ChatController
@RequiredArgsConstructor
@Controller
public class ChatController {

    private final MessageService messageService;

    // /app/chat.sendMessage 로 들어오는 메시지를 처리하여 /topic/public 로 전송
    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(ChatMessage chatMessage) {
        return chatMessage;
    }

    // /app/chat.addUser 로 들어오는 메시지를 처리하여 /topic/public 로 전송
    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(ChatMessage chatMessage) {
        return messageService.createWelcomeMessage(chatMessage);
    }
}
  • @MessageMapping은 클라이언트가 /app/~로 보낸 메시지를 처리하는 역할을 한다.
  • @SendTo는 해당 메서드가 반환된 데이터를 브로커를 통해 구독자에게 전달할 경로를 지정한다.
  1. 즉 클라이언트가 /app/~로 메시지를 보내면
  2. 서버는 해당 메시지를 받고, @MessageMapping에 매핑하여
  3. @SendTo를 통해 브로커에게 요청하여 해당 경로를 구독하고 있는 사용자들에게 메시지를 브로드캐스팅 해준다.

Pub/Sub 구조와 매핑하면


개인적인 회고

  • 처음 강의를 듣고 코드를 짜다 보니 어떤것이 Pub/Sub인지 이 코드가 Pub/Sub 흐름에 어디에 해당하는지를 생각하다 보니까 많이 헷갈렸던거 같다

0개의 댓글