ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Pub-Sub vs. Message Queues
    개발 일지/Back-end 2021. 12. 2. 15:47
    반응형

    1. 개요

    본 포스트에서는 "메시지 큐"와 "Pub/Sub"을 살펴보겠습니다. 이들은 2개 이상의 서비스가 서로 통신하기 위해 분산된 시스템에서 사용되는 일반적인 패턴입니다.

     

    2. 메시지 큐

    메시지 큐는 퍼블리싱 서비스와 큐를 거쳐 동신하는 여러 컨슈머 서비스로 구성됩니다.

    이 통신은 전형적으로 단방향의 특징을 가집니다.

    위 그림에서 퍼블리셔는 'n+1'번째 메시지를 큐에 넣고 있습니다.

    그리고 n개의 메시지들이 이미 큐에 존재하고 있고 소비되기를 기다리고 있습니다.

    우측에는 큐의 메시지를 기다리는 'A'와 'B' 2개의 consuming 서비스를 가지고 있죠.

     

    다음 상황을 고려해 보겠습니다.

    먼저, 퍼블리셔의 메시지가 큐의 마지막에 들어갔습니다.

    다음으로, 우측을 살펴보겠습니다. 소비자 'A'가 메시지 'm1'을 읽었으므로 다른 소비자 'B'가 소비할 큐의 메시지가 없습니다.

     

    2-1. 메시지큐가 사용되는 위치

    메시지 큐는 서비스에서 작업을 위임하려는 경우에 자주 사용됩니다. 이렇게 함으로써 해당 작업이 한번만 실행되도록 보장할 수 있습니다.

     

    마이크로 서비스 아키텍처에서 널리 사용되며 클라우드 기반 또는 서버리스 애플리케이션을 개발하는 동안 부하에 따라 앱을 수평적으로 확장할 수 있습니다.

     

    예를 들어 큐에서 처리 대기 중인 메시지가 많은 경우, 동일한 메시지 큐를 수신하고 메시지 유입을 처리하는 컨슈머 서비스를 가동할 수 있습니다.

    메시지가 처리되면 트래픽이 최소화되어 운영 비용을 절약할 수 있을 때 서비스를 꺼둘 수 있습니다.

     

    2-2. RabbitMQ 예제

    피자 주문을 가정해 보겠습니다.

    사람들은 앱을 통해 피자를 주문하고 가게는 주문을 받을 것입니다.

    이 예시에서 고객(주문자)이 퍼블리셔가 되고 셰프(메이커)가 컨슈머가 됩니다.

     

    먼저, 큐를 정의합니다.

    private static final String MESSAGE_QUEUE = "pizza-message-queue";
    
    @Bean
    public Queue queue() {
        return new Queue(MESSAGE_QUEUE);
    }

    Spring AMQP를 이용해서, "pizza-message-queue"를 생성했습니다.

     

    다음으로 위에서 정의한 큐에 메시지를 게시할 퍼블리셔를 정의합니다.

    public class Publisher {
    
        private RabbitTemplate rabbitTemplate;
        private String queue;
    
        public Publisher(RabbitTemplate rabbitTemplate, String queue) {
            this.rabbitTemplate = rabbitTemplate;
            this.queue = queue;
        }
    
        @PostConstruct
        public void postMessages() {
            rabbitTemplate.convertAndSend(queue, "1 Pepperoni");
            rabbitTemplate.convertAndSend(queue, "3 Margarita");
            rabbitTemplate.convertAndSend(queue, "1 Ham and Pineapple (yuck)");
        }
    }

    Spring AMQP는 구성 오버헤드를 줄이기 위해 RabbitMQ 교환에 연결되는 RabbitTemplate 빈을 생성합니다.

    퍼블리셔는 이를 사용하여 큐에 3개의 메시지를 전송합니다.

     

    이제 피자 주문이 들어왔으므로 별도의 컨슈머 애플리케이션이 필요합니다.

    큐에서 메시지를 읽을 것입니다.

    public class Consumer {
        public void receiveOrder(String message) {
            System.out.printf("Order received: %s%n", message);
        }
    }

     

    이제 리플렉션을 사용하여 컨슈머의 수신 순서 메서드를 호출하는 큐에 대한 MessageListenerAdapter를 생성합니다.

    @Bean
    public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames(MESSAGE_QUEUE);
        container.setMessageListener(listenerAdapter);
        return container;
    }
    
    @Bean
    public MessageListenerAdapter listenerAdapter(Consumer consumer) {
        return new MessageListenerAdapter(consumer, "receiveOrder");
    }

    큐에서 읽은 메시지는 이제 컨슈머 클래스의 receiveOrder 메서드로 라우팅됩니다.

    이 애플리케이션을 실행하기 위해, 들어오는 주문을 이행하고자 하는 만큼 컨슈머 애플리케이션을 생성할 수 있습니다.

    예를 들어 피자 주문 400개가 들어오면 1명 이상의 컨슈머(메이커)가 필요합니다. (너무 적으면 주문 처리가 느려질 수 있습니다.)

     

    3. Pub-Sub

    메시지 큐와는 반대로, pub-sub 아키텍처에서 모든 소비(구독) 애플리케이션이 퍼블리셔가 교환에 게시하는 메시지 카피를 최소 1개는 가져오길 원합니다.

    왼쪽에는 "n+1" 메시지를 토픽으로 보내는 퍼블리셔가 있습니다.

    이 토픽은 해당 메시지를 구독(subscription)에 브로드캐스트 합니다.

    이러한 구독은 큐에 바인딩됩니다. 각 큐에는 메시지를 기다리는 수신 구독자 서비스가 있습니다.

     

    이제 일정 시간이 지난후 다음 상황을 고려해 보겠습니다.

    두 구독 서비스 모두 이 메시지의 카피를 받았기 때문에 "m1"을 소비하고 있습니다.

    그리고 토픽은 모든 구독자에게 새로운 메시지 "n+1"을 배포하고 있습니다.

     

    Pub-Sub 은 각 구독자(Subscriber)가 메시지 카피를 가져오도록 보장해야 하는 경우 사용합니다.

     

    3-1. RabbitMQ 예제

    이번에는 의류 웹사이트를 예시로 들겠습니다.

    이 웹사이트는 거래에 대해 알리기 위한 푸시 알림을 사용자들에게 보낼 수 있습니다.

     

    이때 이메일이나 문자 알림을 통해 알림을 보낼 수 있습니다.

    이 시나리오에서 웹 사이트는 퍼블리셔이고, 문자 및 이메일 알림 서비스는 구독자가 됩니다.

     

    먼저, 토픽(교환)을 정의하고 2개 큐(이메일, 문자)를 바인딩합니다.

    private static final String PUB_SUB_TOPIC = "notification-topic";
    private static final String PUB_SUB_EMAIL_QUEUE = "email-queue";
    private static final String PUB_SUB_TEXT_QUEUE = "text-queue";
    
    @Bean
    public Queue emailQueue() {
        return new Queue(PUB_SUB_EMAIL_QUEUE);
    }
    
    @Bean
    public Queue textQueue() {
        return new Queue(PUB_SUB_TEXT_QUEUE);
    }
    
    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(PUB_SUB_TOPIC);
    }
    
    @Bean
    public Binding emailBinding(Queue emailQueue, TopicExchange exchange) {
        return BindingBuilder.bind(emailQueue).to(exchange).with("notification");
    }
    
    @Bean
    public Binding textBinding(Queue textQueue, TopicExchange exchange) {
        return BindingBuilder.bind(textQueue).to(exchange).with("notification");
    }

    이제 라우팅 키 "notification"을 사용하여 2개의 큐를 바인딩했습니다. 즉, 이 라우킹 키로 토픽에 게시된 모든 메시지가 각 큐로 이동합니다. 

     

    앞서 만든 Publisher 클래스를 업데이트하여 다음과 같이 Exchange에 몇 가지 메시지를 보낼 수 있습니다.

    public class Publisher {
    
        private RabbitTemplate rabbitTemplate;
        private String exchange;
    
        public Publisher(RabbitTemplate rabbitTemplate, String exchange) {
            this.rabbitTemplate = rabbitTemplate;
            this.exchange = exchange;
        }
    
        @PostConstruct
        public void postMessages() {
            rabbitTemplate.convertAndSend(topic, "notification", "New Deal on T-Shirts: 95% off!");
            rabbitTemplate.convertAndSend(topic, "notification", "2 for 1 on all Jeans!");
        }
    }

     

    4. 비교하기

    2개를 모두 살펴보았는데, 이제 정리해 보겠습니다.

     

    먼저, 메시지 큐와 pub-sub 아키텍처 패턴은 애플리케이션을 수평적으로 확장할 수 있도록 도와주는 분할 기법입니다.

     

    다음으로, pub-sub과 메시지 큐는 통신할 때 기존의 동기식 모드보다 내구성이 좋다는 것입니다.

    예를 들어 앱 A가 HTTP 호출을 통해 비동기로 앱 B와 통신하는 경우, 애플리케이션 중 하나가 다운되면 데이터가 손실되고 다시 요청해야 합니다.

     

    메시지 큐를 사용할 경우, 컨슈머 애플리케이션 인스턴스가 다운되면 다른 컨슈머가 대신 메시지를 처리할 수 있습니다.

    pub-sub 을 사용할 경우, 구독자가 다운되면 메시지를 복구하고 구독 큐에서 사용할 수 있습니다.

     

    마지막으로 컨텍스트가 중요합니다.

    pub-sub 또는 메시지 큐 아키텍처를 사용할지 정하는 것은, 소비 서비스가 작동하는 방식을 정확히 정의해야 합니다.

    중요한 요소는 "모든 소비자가 모든 메시지를 받는 것이 중요한가?" 라는 질문입니다. 

     

     

    반응형
Designed by Tistory.