STOMP이란 무엇인가?
STOMP (Simple/Streaming Text Oriented Messaging Protocol)
--
STOMP은
웹소켓(WebSocket) 또는 TCP처럼 "양방향" 네트워크 프로토콜 위에서 동작하는
텍스트 기반의 메시징 서브 프로토콜로,
"메시지 브로커"라는 것을 활용하여 보다 쉽게 메시지를 송수신할 수 있는 프로토콜이다.
(즉, STOMP은 메시지 브로커와 통신하기 위해 설계된 프로토콜이다.)
그리고 "pub/sub(발행/구독)" 구조로 동작한다.
서브 프로토콜은
상위 프로토콜 위에서 동작하는 프로토콜로,
특정 기능을 확장하거나 보완하는 역할을 해준다.
WebSocket과 STOMP의 차이점
WebSocket
TCP 기반의 양방향 통신 프로토콜로,
클라이언트와 서버 간에 지속적인 연결을 유지하며,
데이터를 실시간으로 주고받는 데 최적화되어 있다.
- 클라이언트와 서버 간의 양방향 통신 가능
- 프로토콜 레벨의 통신만 제공 (메시지 형식, 라우팅 등의 표준화가 없다.)
- 단순히 데이터 프레임을 송수신하는 데 초점 되어 있다.
STOMP
텍스트 기반의 메시징 프로토콜로,
브로커와의 통신을 표준화하기 위해 설계되었다.
WebSocket과 같이 양방향 네트워크 위에서 동작할 수 있으며,
메시징 시스템을 더 쉽게 구축하기 위해 사용한다.
- 메시지 포맷 표준화
- 주제 기반의 라우팅 가능 (ex /topic/chatroom/1)
- 메시지 "구독(Subsribe)" 및 "발행(Publish)" 메커니즘을 지원
- 브로커 가용 가능 (메시지 관리 및 로드 분산)
왜 STOMP을 사용하는가?
SebSocket의 단점
- 송수신할 때 전달하는 메시지에 대한 형식이 정해져 있지 않고,
단순히 데이터 프레임을 주고받기 위한 프로토콜로,
메시지 구조를 정의하거나 해석하는 기능이 없어서
개발자가 직접 메시지의 형식과 라우팅 로직을 설계하여 규칙을 정하고 약속해야 한다.
즉, 어떤 요청은 어떤 메시지 형식으로 보낼지, 누구에게 보낼지 등 규칙을 별도로 구현해야 한다. - 다수의 클라이언트와 통신하거나 메시지 브로드캐스팅을 구현하는 것을 거의 직접 구현해야 하므로,
간단한 애플리케이션을 개발할 때에는 크게 상관이 없지만,
규모가 조금씩 커진다면 직접 구현해야 할 로직들이 많아지게 된다.
(특정 사용자에게만 메시지를 전달하거나 특정 그룹에 메시지를 브로드캐스트 하려면 추가적인 개발 필요) - 순수 WebSocket만으로는 다수의 클라이언트나 여러 "주제(Topic)"를 다루기 어렵기 때문에
메시지 브로커 없이 처리하면 서버에 과부하가 걸릴 가능성이 크다.
STOMP을 사용하는 이유
- 송수신할 메시지 구조가 표준화(커맨드, 헤더, 바디)되어 있어 개발자가 메시지를 보다 쉽게 처리할 수 있게 된다.
- "주제(Topic)"를 기반으로 메시지를 라우팅 할 수 있어서
특정 방이나 사용자 그룹에게 메시지를 전달하는 작업이 단순화된다. - 브로커를 지원하므로 확장성을 제공하고,
이를 통해 메시지 큐잉, 로드 분산, 메시지 보존 등 고급 기능을 구현할 수 있다. - 구독(Subscribe)이라는 기능을 제공하여, 각 클라이언트가 특정 "주제"를 "구독"하면
그 주제로 들어오는 메시지만 받을 수 있으며,
특정 "주제"를 "구독"한 다른 클라이언트들에게도 메시지를 전달할 수 있다.
[송수신되는 메시지 구조]
COMMAND (커맨드)
header1: value1 (헤더)
header2: value2
Body^@ (바디)
[객체(DTO)로 전달하는 예시 메시지 형식]
SEND
destination: /topic/chat/room/3827
content-type: application/json
{"roomNumber": 3827, "sender": "player1", "message": "Hello it's me"}
^@
[텍스트(문자열)로 전달하는 예시 메시지 형식]
SEND
destination: /topic/chat/room/3827
content-type: text/plain
Hello it's me
용어 정리
브로커 (Broker)
클라이언트와 서버 사이에서 메시지 전달을 중계하는 역할로,
정확히는 "발행자(Publisher)"와 "구독자(Subscriber)" 사이에서 메시지를 중계하는 역할을 하는 소프트웨어다.
이때 메시지를 한 곳에서 다른 곳으로 전달해 주며, 메시지의 라우팅, 큐잉, 저장 및 전달을 저리 해준다.
간단하게 요약하면
클라이언트가 보내는 메시지를 받아서 다른 클라이언트에게 전달하는 역할을 한다.
+
브로커는 일반적으로 서버 안에 내장되어 있는 내장 브로커와
서버 외부에서 별도로 존재하는(독립적인) 외부 브로커로 나누어진다.
(Spring의 WebSocket 환경에서는 브로커가 서버에 내장된 형태로 구현되는 경우가 많다.)
구독 (Subscribe)
클라이언트가 특정 주소(경로)를 자신의 경로로 지정하는 것을 의미한다.
해당 주소(경로)는 다른 클라이언트가 메시지를 전달할 목적지를 가리키는 의미이기도 해서
해당 주소로 전달하는 메시지는 전부 해당 클라이언트에서 수신하겠다는 의미기도 하다.
예시 주소(경로)
"/topic/room/number/2322"
만약 클라이언트 A, B, C가 위 예시 주소를 구독하고 있다고 가정하고,
클라이언트 D가 어떤 메시지를 "/topic/room/number/2322"로 전달하면
클라이언트 A, B, C 모두 해당 메시지를 전달받게 된다.
(만약 클라이언트 B가 전달해도 A, B, C 모두에게 전달되니 자신이 보낸 메시지를 자신도 받는다.)
발행 (publish)
클라이언트가 특정 주소(경로)로 메시지를 전송하는 행위를 의미한다.
즉, 클라이언트가 메시지를 발행하면
해당 목적지(주소)를 구독하고 있는 클라이언트(구독자)들에게 해당 메시지가 전달된다.
목적지 ( Destination )
메시지를 전달할 대상 경로(주소)를 의미하는 것으로
메시지가 향할 주소를 가리킨다.
즉, 클라이언트가 메시지를 보내거나, 받을 경로로 사용된다.
위에 "구독" 설명에서 구독하는 경로가 자신을 가리키는 목적지 주소가 된다.
클라이언트 A가 "/topic/room/number/2322"를 구독하면
해당 주소가 클라이언트 A를 가리키는 목적지가 된다.
클라이언트 B가 메시지를 "/topic/room/number/2322"를 구독하고 있는 클라이언트들에게 전달 요청을 하면
해당 메시지는 클라이언트 A에게 전달된다.
주제 (Topic) & 큐 (Queue)
주제는
1:N 방식으로 클라이언트와 서버 간에 실시간 메시지를 전달하는 역할을 수행하는 채널을 의미한다.
즉, 하나의 클라이언트가 메시지를 브로드캐스트 방식으로 여러 클라이언트에게 전달하는 동작을 수행한다.
큐는
브로드캐스트 방식을 사용하지 않아서 1:1 방식으로 메시지를 전달하는 채널을 의미한다.
즉, 하나의 클라이언트에게만 전달되며, 만약 여러 클라이언트가 동일한 큐를 구독하더라도,
그중에서 하나의 클라이언트에게만 메시지가 전달된다.
여기서 누구에게 메시지를 전달할지는 브로커의 배분 방식에 따라 달라진다.
[ 대표적인 배분 방식 ]
- 라운드 로빈 : 요청할 때마다 돌아가면서 전달받기
- 부하 기반 방식 : 클라이언트의 처리량이나 상태를 기준으로 전달 (A가 바쁘면 B에게 전달)
정리하면
주제 : 메시지를 다수의 클라이언트에게 동시에 전달되도록 설계된 구조
큐 : 메시지를 1명의 클라이언트에게 전달되도록 설계된 구조
여기서 개발자 간의 약속이 생기게 되었다.
클라이언트들이 구독하는 주소에서 시작을
"/topic"으로 시작하면 주제 방식으로 처리하고,
"/queue"로 시작하면 큐 방식으로 처리하기로 약속을 했다.
(구독 중인 주소만 보고 어떤 방식으로 응답 받았는지 쉽게 파악하기 위해서)
즉, 구독중인 목적지 주소의 접두사(시작 부분)를 "/topic" 또는 "/queue"로 지정해서
해당 접두사만 보고 주제 방식으로 응답받는 주소인지, 큐 방식을 응답 받는 주소인지 파악할 수 있게 된다.
추가로
주제는 "/topic" 접두사로, 큐는 "/queue" 접두사로 설정하고
목적지가 "/topic/room/number/2343"와 "/queue/room/game" 이렇게 클라이언트에서 설정(구독)했다고 하면
"/topic/room/number/2343"을 주제, "/queue/room/game"를 큐라고 부르며
이를 통틀어서 목적지라고 부른다.
목적지 : 메시지를 발행하거나 구독하는 경로
주제 : 목적지에서 "/topic"으로 시작하는 경로
큐 : 목적지에서 "/queue"로 시작하는 경로
이것은 약속일뿐이지 접두사를 "/topic"으로 했다고 자동으로 1:N 브로드캐스트로 전달되거나
"/queue"로 했다고 자동으로 1:1 전달이 이루어지는 것은 아니고
개발자가 어떤 접두사를 사용하면 브로커에서 어떻게 처리할지 설정해줘야 한다.
--
STOMP 동작 흐름
--
STOMP의 흐름은 두 가지 주요 방식으로 나눠진다.
- "/app" 접두사로 요청 시 "WebSocketConfig"로 인해 서버의 컨트롤러를 거쳐가는 과정이 추가된다.
- "/topic" 접두사로 요청 시 브로커가 바로 해당 경로를 구독하는 구독자에게 전달한다.
"/app" 접두사를 사용하는 경로는 서버의 컨트롤러로 전달하는 경로로
간단하게 "HTTP Patch"경로로 서버를 호출하는 것과 비슷한 느낌이다.
여기서 "/app" 또한 "/topic", "/queue"처럼 일종의 약속일뿐
어떠한 접두사를 사용해도 상관이 없다.
(어떤 접두사든 어떤 동작을 하는지를 설정해줘야 하기 때문이다.)
"/app" 접두사의 요청 흐름
- [request]
발신자(클라이언트A)가 "/app/room/number"로 메시지 전달 요청 - [SimpAnnotationMethod]
controller에서 @MessageMapping("/app/room/number")로 설정된 메서드를 호출 - [Controller]
호출된 메서드로 로직을 처리 후 @SentTo("/topic/room/number/2343")로 브로커에게 응답 메시지 전달 - [Broker]
서버에서 클라이언트로 메시지를 전달하는 중간 역할로,
서버로부터 받은 메시지를 "/topic/room/number/2343"를 목적지로 전달 요청 - [SimpleBroker]
"/topic/room/number/2343"를 구독하고 있는 모든 클라이언트에게 전달
"/topic" 접두사의 요청 흐름
- [request]
발신자(클라이언트A)가 "/topic/room/nubmer/2343"로 메시지 전달 요청 - [SimpleBroker]
"/topic/room/number/2343"를 구독하고 있는 모든 클라이언트에게 전달
채널 (Channel)
클라이언트와 서버 간의 메시지가 흐르는 경로를 의미하는 것으로,
STOMP 메시지는 특정 채널(경로)을 통해 송수신된다.
가끔 "채널을 구독한다."와 "목적지(주제, 큐)를 구독한다."를 사용해서
채널 = 목적지라고 생각할 수 있지만 기술적으로 보면 다르다.
채널을 구독한다. = 클라이언트가 메시지를 수신하기 위해 WebSocket 채널에 연결한 상태
목적지를 구독한다. = 특정 목적지("/topic/game/updates")를 명시적으로 구독하는 상태
즉, 일상 대화나 학습 상황에서는 두 말을 혼용해서 사용해도 크게 문제 되지는 않지만
기술적으로 정확한 문맥을 작성해야 하는 문서 작성이나 설계 및 운영 부분에서는 꼭 구분해서 사용해야 한다.
--
'Terminology' 카테고리의 다른 글
웹소켓 (WebSocket) (0) | 2024.11.25 |
---|---|
SOP와 CORS (+ Origin) (0) | 2024.11.06 |
[디자인 패턴] 팩토리 메서드 패턴 (Factory Method) (1) | 2024.11.01 |
[디자인 패턴] 싱글톤 패턴 (Singleton) (0) | 2024.10.30 |
디자인 패턴 (1) | 2024.10.28 |