-
Spring WebSocket External Broker 적용(ActiveMQ)프로젝트/WebSocket 2023. 7. 21. 00:01728x90
개요
In-memory Broker인 SimpleBroker를 사용하게 되면 Server Instance가 여러 대 존재할 때 상태에 대한 동기화가 제대로 이루어지지 않을 수 있습니다.
예를 들어, A유저는 Server1에서 웹소켓을 연동하였고, B유저는 Server2에서 웹소켓을 연동하였습니다. 만약 같은 /topic/same을 바라본다고 하더라도 Server1에서 message를 보내면 A만 받을 수 있을 것이며, Server2에서 message를 보내면 B만 받을 수 있습니다.
이에 따라 External Broker를 적용해보고자 합니다.
RabbitMQ, ActiveMQ 등을 적용해 볼 수 있습니다.
External Broker를 활용한 STOMP 구조도
SimpleBroker를 이용한 구조에서 바뀐 부분은 바로 StompBrokerReplay가 MessageHandler부분에 적용되는 것입니다.
Broker Relay를 사용하여 TCP를 통해 외부 STOMP 브로커로 메시지를 전달하고 브로커에서 가입한 클라이언트로 메시지를 전달합니다.
//Simple Broker를 사용하는 경우 config.enableSimpleBroker("/queue") //Relay를 활용하여 External Broker를 사용하는 경우 config.enableStompBrokerRelay("/queue");
StompBrokerRelayMessageHandler 동작과정
1. CONNECT 메시지마다 브로커와 TCP 연결 수립 (session-id로 식별)
2. 모든 메시지 브로커로 전달
3. 브로커로부터 받은 메시지를 WebSocket 세션을 통해 클라이언트에게 전달
RabbitMQ vs ActiveMQ
STOMP를 지원하는 Message Broker 중 RabbitMQ와 ActiveMQ를 비교해보고자 합니다.
Spring docs에서도 External Broker의 예시로 RabbitMQ와 ActiveMQ 등등.. 으로 표기합니다.
RabbitMQ는 오픈소스 메시지 브로커로서 AMQP 프로토콜을 구현합니다.
plugin을 통해 MQTT, STOMP등의 프로토콜을 사용할 수 있습니다.
처리량이 많고 지연 시간이 짧은 메시징을 위해 설계되었습니다.
ActiveMQ는 JMS API를 지원하는 오픈소스 메시지 브로커입니다.
AMPQ, MQTT, STOMP 등 다양한 메시징 프로토콜을 지원합니다.
RabbitMQ은 단순성과 사용 편의성이 강조되며, ActiveMQ는 학습곡선이 조금 존재하지만 세분화된 제어, 고급 메시지 기능이 존재합니다.
더 다양한 기능으로 추후의 요구사항을 반영할 수 있는 ActiveMQ를 활용해 보겠습니다.
Docker Compose로 ActiveMQ 띄우기
version: '3.7' services: activemq: image: rmohr/activemq ports: - 8161:8161 - 61613:61613
http://localhost:8161/
admin, admin입력하여 접속
Spring MessageHanlder 코드 변경
override fun configureMessageBroker(config: MessageBrokerRegistry) { config.setApplicationDestinationPrefixes("/pub") config.enableStompBrokerRelay("/sub") .setRelayHost("localhost") .setRelayPort(61613) .setClientLogin("admin") .setClientPasscode("admin") // config.enableSimpleBroker("/sub") }
enableSimpleBroker는 주석처리하고, enableStompBrokerRelay로 설정하였습니다.
Caused by: java.lang.IllegalStateException: No compatible version of Reactor Netty
이후 실행하면 위와 같은 예외가 발생합니다.
reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", classLoader); reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", classLoader);
예외난 부분의 코드를 타고 들어가 확인해 보니 내부적으로 Reactor Nettry가 외부 메시지 브로커와의 연결 및 통신 설정을 위해 기본적으로 사용되고, reactor.netty와 관련된 의존성이 없어 발생한 예외 같습니다.
implementation("org.springframework.boot:spring-boot-starter-reactor-netty")
위의 의존성 추가 후 다시 실행
Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. Check whether you have a dependency on 'io.netty:netty-resolver-dns-native-macos'
implementation("io.netty:netty-resolver-dns-native-macos:4.1.72.Final:osx-aarch_64")
github issues를 살펴보니 netty를 사용할 때 MacOS에서 DNS 문제를 해결하기 위해 위의 의존성을 추가하면 된다고 합니다.
TCP connection failure in session _system_: Failed to connect: Connection refused: localhost/127.0.0.1:61613
docker부분에서 port binding 하는 부분에 61616으로 되어 있어 61613으로 수정 후 잘 실행됩니다.
Client로 접속하여 Connect!
ActiveMQ에 Queue가 하나 생겼다
Test로 해당 토픽을 Consumer가 구독하도록 하고, 메시지를 발행하면?
메시지가 정상적으로 도달하여 External Broker가 잘 동작하는 것이 확인가능하다.
참고자료
https://docs.spring.io/spring-framework/reference/web/websocket/stomp/handle-broker-relay.html
https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html
https://www.rabbitmq.com/documentation.html
https://www.designgurus.io/blog/RabbitMQ-Kafka-ActiveMQ-System-Design
'프로젝트 > WebSocket' 카테고리의 다른 글
Spring Boot WebSocket with Kafka (0) 2023.07.28 RSocket이란? (0) 2023.07.20 WebSocket Scale Out - 이론편 (0) 2023.06.24 TCP Socket vs WebSocket (0) 2023.06.23 Spring WebSocket 활용 (0) 2023.06.12