Spring STOMP/WebSocket에 RabbitMQ를 사용해 분산, 이중화 환경에서 데이터를 동기화해보자

개발자 이상규·2024년 1월 30일

Architecture

목록 보기
2/4
post-thumbnail

RabbitMQ


https://oingdaddy.tistory.com/166


메세지 브로커로 RabbitMQ가 필요한 이유


확장성 및 유연성
RabbitMQ는 메시지 큐 시스템으로서, 분산 환경에서 메시지 전달을 지원하고 여러 개의 애플리케이션 간 통신을 가능하게 합니다.
다수의 웹 소켓 클라이언트 및 백엔드 서비스 간의 통신을 용이하게 만들어 확장성과 유연성을 제공합니다.

메시지 라우팅 및 패턴 매칭
RabbitMQ는 메시지 라우팅을 위한 다양한 패턴을 제공합니다. 메시지 라우팅을 통해 특정 주제 또는 대상에 대한 메시지를 효율적으로 처리할 수 있습니다.

Pub-Sub 패턴 지원
RabbitMQ는 발행-구독 (Pub-Sub) 패턴을 지원하여 여러 클라이언트가 메시지를 동시에 수신할 수 있도록 합니다. 이는 다수의 WebSocket 클라이언트에 실시간 데이터를 효과적으로 배포하는 데 도움이 됩니다.

분산 아키텍처 지원
RabbitMQ는 분산 아키텍처를 지원하므로 여러 노드 간에 메시지를 전송할 수 있습니다. 이는 대규모 및 분산된 시스템에서 중요한 기능입니다.


도커 설치법

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -p 61613:61613 --restart=unless-stopped rabbitmq:management

  • 15672 포트 : Dashboard
  • 61613 포트 : RabbitMQ STOMP 기본 포트

docker exec rabbitmq rabbitmq-plugins enable rabbitmq_stomp

RabbitMQ stomp 플러그인 활성화


Destination 종류


/topic (Broadcast)

  • /topic은 STOMP 클라이언트에서 가장 일반적으로 사용되는 목적지 유형이며, /topic/ 형식이다. 이것은 구독자 패턴에 대한 토픽 매칭을 수행하며, 메시지를 여러 구독자에게 라우팅할 수 있다.

/exchange (Multicast)

  • SUBSCRIBE 프레임에서는 /exchange/<name>/<pattern> 형식의 목적지를 사용할 수 있다.
    • <pattern>이 제공된 경우, 큐를 <name> Exchange에 <pattern>으로 바인딩하고 큐 구독을 등록한다.
  • SEND 프레임에서는 /exchange/<name>/<routing-key>의 형식을 사용한다.
    • <routing-key>를 사용하여 Exchange <name>으로 전송한다.
  • 각 구독자마다 새 큐가 생성되어 지정된 Exchange에 라우팅 키를 사용하여 바인딩된다. 기존 큐를 사용하려면 /amq/queue 목적지를 사용해야 한다.

/queue (Unicast)

  • /queue는 간단한 대기열을 처리하기 위해 사용된다. /queue/ 형식을 사용하면 좋다. 큐 목적지는 각 메시지를 최대 한 명의 구독자에게 전달하며, 구독자가 없으면 구독자가 큐에 연결할 때까지 큐에 대기한다
    • SUBSCRIBE 프레임에서는 의 공유 큐를 생성하고 현재 STOMP 세션에 대해 의 공유 큐를 구독한다.

유니캐스트, 멀티캐스트, 브로드캐스트 비교

  • 유니캐스트(Unicast) : 1 대 1(특정 단일)
  • 브로드캐스트(Broadcast) : 1 대 다수(불특정 다수)
  • 멀티캐스트(Multicast) : 1 대 다수(특정 집단)

Exchange 종류


Producer의 메시지들을 어떤 Queue로 발송할지 결정, 일종의 Router개념으로 4가지 타입이 있습니다.

Fanout Exchange (Broadcast)

  • Exchange Type: Fanout
  • 용도: 이 exchange는 메시지를 전체 시스템에 브로드캐스트하는 데 사용됩니다. 모든 채팅 클라이언트가 메시지를 수신할 수 있도록 합니다.
@Bean
public FanoutExchange chatFanoutExchange() {
    return new FanoutExchange("<exchange 명>");
}

Direct Exchange (Private Messages)

  • Exchange Type: Direct
  • 용도: 이 exchange는 특정 사용자 간의 개별적인(private) 메시지 전송에 사용됩니다.
@Bean
public DirectExchange chatDirectExchange() {
    return new DirectExchange("<exchange 명>");
}

Topic Exchange (Group Chat)

  • Exchange Type: Topic
  • 용도: 이 exchange는 특정 주제나 그룹에 속한 사용자들에게 메시지를 전송하는 데 사용됩니다.
@Bean
public TopicExchange chatTopicExchange() {
    return new TopicExchange("<exchange 명>");
}

Queue


용도: Fanout Exchange나 Direct Exchange에서 메시지를 받기 위한 용도로 사용됩니다.
기본적으로 RabbitMQ에서 STOMP로 클라이언트가 Subscribe한다면 각 클라이언트마다 큐는 자동으로 동적으로 생성이 됩니다.
하지만 필요에 의해 메세지 데이터 저장, application 마다 데이터 처리 등 이 필요할때 사용하면 됩니다.

@Bean
public Queue queue() {
    return new Queue("<큐 이름>", true);
}

Binding


정의한 Queuesms exchange에 바인딩되어 메시지를 수신할 수 있도록 설정되어야 합니다

@Bean
public Binding binding(Queue queue, FanoutExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange);
}

@Bean
public Binding binding(Queue queue, DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("<routing-key>");
}

@Bean
public Binding binding(Queue queue, TopicExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("<routing-key>");
}

모든 인스턴스가 RabbitMQ로 채팅 데이터를 전달 받지 않고 정확히 클라이언트가 접속되어 있는 인스턴스에만 데이터를 정확히 전송하려고 합니다.

1. 클라이언트 식별 및 라우팅
- 클라이언트가 접속할 때, 해당 클라이언트를 식별할 수 있는 유일한 식별자를 부여합니다. 이는 클라이언트의 세션 ID, 사용자 ID, 또는 다른 식별자일 수 있습니다.
2. Exchange 및 Queue 구성
- 각 클라이언트에 대해 개별적인 exchange와 queue를 생성합니다. 이때, exchange는 direct exchange로 설정하고, 각 클라이언트의 식별자를 라우팅 키로 사용합니다.

@Bean
public DirectExchange chatDirectExchange() {
    return new DirectExchange("<exchange 이름>");
}

3. 클라이언트에게 메시지 전송

  • 클라이언트가 접속한 인스턴스에서 메시지를 수신하고, 해당 메시지를 클라이언트의 exchange로 라우팅하여 전송합니다.
rabbitTemplate.convertAndSend("<exchange 이름>", "<클라이언트의 식별자>", "<전송할 메시지>");

이러한 구조를 통해 클라이언트가 접속한 인스턴스에만 메시지가 전달되고, 다른 인스턴스는 해당 메시지를 수신하지 않게 됩니다. 각 Application 인스턴스는 자신의 exchange와 queue를 가지고 있어서, 메시지가 해당 클라이언트로 정확히 라우팅됩니다.

이와 같은 방식으로 RabbitMQ를 이용하면 클라이언트가 연결된 인스턴스에만 데이터를 전송할 수 있으며, 불필요한 데이터 전송을 방지할 수 있습니다.

4. 메시지 수신 (Consumer):

  • 각 클라이언트는 자신의 queue에서 메시지를 수신합니다. 이때, @RabbitListener 어노테이션을 사용하여 특정 Queue에서 메시지를 수신하는 메소드를 정의합니다.
/exchange/<exchange >/<routing-key>

따라서, 각 클라이언트는 자신의 exchange와 queue를 가지고 있어서 메시지가 해당 클라이언트로 정확히 라우팅됩니다. 다른 클라이언트가 접속한 인스턴스는 자신의 exchange와 queue에만 메시지를 수신하기 때문에, 클라이언트가 연결된 인스턴스에만 데이터가 전송되고, 불필요한 데이터 전송을 방지할 수 있습니다.





reference :

profile
Contact: leeeesanggyu@gmail.com

0개의 댓글