서비스를 개발하면서 API-Gateway에 토큰 캐시를 구축하기 위해 RabbitMQ를 공부했던 내용을 정리해 보았습니다.

RabbitMQ는 AMQP를 따르는 오픈소스 메세지 브로커인데, 메세지를 많은 사용자에게 전달하거나, 요청에 대한 처리 시간이 길 때, 해당 요청을 다른 API에게 위임하고 빠른 응답을 할 때 많이 사용합니다. 또한, MQ를 사용하여 애플리케이션 간 결합도를 낮출 수 있는 장점도 있습니다.

이번 프로젝트에서는 RabbitMQ sever를 쿠버네티스 클러스터에 배포하여 사용하였기 때문에, server 설치에 대한 내용은 따로 다루지 않겠습니다.

Core Concept

RabbitMQ에서 중요한 개념으로는 Producer, Consumer, Queue, Exchange, Binding이 있습니다.

RabbitMQ Message Flow

먼저 Producer는 이름에서 알 수 있듯이, 메세지를 생성하고 발송하는 주체입니다. 이 메세지는 Queue에 저장이 되는데, 주의할 점은 Producer는 Queue에 직접 접근하지 않고, 항상 Exchange를 통해 접근하게 됩니다.

다음으로는, Consumer가 있으며, 메세지를 수신하는 주체입니다. Consumer는 Queue에 직접 접근하여 메세지를 가져옵니다.

Queue는 Producer들이 발송한 메세지들이 Consumer가 소비하기 전까지 보관되는 장소입니다. Queue는 이름으로 구분되는데, 같은 이름과 같은 설정으로 Queue를 생성하면 에러 없이 기존 Queue에 연결되지만, 같은 이름과 다른 설정으로 Queue를 생성하려고 시도하면 에러가 발생합니다.

Exchange는 Producer들에게서 전달받은 메세지들을 어떤 Queue들에게 발송할지를 결정하는 객체입니다. Exchange는 네 가지 타입이 있으며, 일종의 라우터 개념입니다.

Binding은 Exchange에게 메세지를 라우팅 할 규칙을 지정하는 행위입니다. 특정 조건에 맞는 메세지를 특정 큐에 전송하도록 설정할 수 있는데, 이는 해당 Exchange 타입에 맞게 설정되어야 합니다. (Exchange와 Queue는 m:n binding이 가능합니다.)

Exchange에는 네 가지 타입이 있는데, 각 타입별 특징은 아래와 같습니다.

타입 설명 특징
Direct Routing key가 정확히 일치하는 Queue에 메세지 전송 Unicast
Topic Routing key 패턴이 일치하는 Queue에 메세지 전송 Multicast
Headers [key:value]로 이루어진 header 값을 기준으로 일치하는 Queue에 메세지 전송 Multicast
Fanout 해당 Exchange에 등록된 모든 Queue에 메세지 전송 Broadcast

​ * Headers 타입의 경우, 모든 header가 일치할 때, 하나라도 일치할 때 메세지 전송 등의 옵션을 줄 수 있습니다.

Direct Exchange

Direct Exchange는 라우팅 키를 이용하여 메세지를 라우팅 하는데, 하나의 큐에 여러 개의 라우팅 키를 지정할 수 있습니다.

Direct Exchange multiple binding

위 그림은 error 메세지만 저장소에 기록하고, info와 warning을 포함한 모든 정보를 디스플레이에 출력할 때를 나타내는 모식도입니다. (C1 : 저장소, C2 : 디스플레이)

Direct Exchange에 여러 큐에 같은 라우팅 키를 지정하여 Fanout처럼 동작하게 할 수도 있습니다.

Direct Exchange like Fanout


* RabbitMQ에는 Default Exchange(Unnamed Exchange)가 있는데, 이 Exchange는 Direct타입이며, RabbitMQ에서 생성되는 모든 Queue가 자동으로 binding됩니다. 이 때, 각 Queue의 이름이 라우팅 키로 지정됩니다.

Topic Exchange

Topic Exchange는 라우팅 키 패턴을 이용하여 메세지를 라우팅합니다.

Topic Exchange

라우팅 키가 example.orange.rabbit 인 경우 메세지가 Q1, Q2에 모두 전달되고,

라우팅 키가 example.orange.turtle 인 경우 메세지가 Q1에만 전달이 됩니다.

라우팅 키가 lazy.grape.rabbit인 경우엔 메세지가 Q2에 한 번만 전달이 됩니다. (라우팅 패턴이 여러 개 일치하더라도 하나의 큐에는 메세지가 한 번만 전달됩니다.)

Headers Exchange

Headers Exchange는 Topic Exchange와 유사하지만 라우팅을 위해 header를 쓴다는 차이점이 있습니다.

Headers Exchange

Fanout Exchange

Fanout Exchange는 exchange에 등록된 모든 queue에 메세지를 전송합니다.

Fanout Exchange

Message Queue 및 Message 보존

RabbitMQ server가 종료(어떤 이유에서든지)후 재기동하면, 기본적으로 Queue는 모두 제거됩니다. 이를 막기 위해서는 Queue를 생성할 때, Durable 옵션에 true를 주고 생성해야 하며, Producer가 메세지를 발송할 때, PERSISTENT_TEXT_PLAIN 옵션을 주어야 메세지가 보존됩니다.

Queue 생성 예시

rabbitmqChannel.queueDeclare(rabbitmqQueueName, true, false, false, null); //QueueName 다음의 true가 durable option

Message 발송 예시

rabbitmqChannel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

Prefetch Count

하나의 Queue에 여러 Consumer가 존재할 경우, Queue는 기본적으로 Round-Robin 방식으로 메세지를 분배합니다.

그런데, Consumer가 2개인 상황에서 홀수번째 메세지는 처리 시간이 짧고, 짝수번째 메세지는 처리 시간이 매우 긴 경우, 계속해서 하나의 Consumer만 일을 하게 되는 상황이 발생할 수 있습니다.

이를 예방하기 위해, prefetch count를 1로 설정해 두면, 하나의 메세지가 처리되기 전(Ack를 보내기 전)에는 새로운 메세지를 받지 않게 되므로, 작업을 분산시킬 수 있습니다.

Reference

  • https://www.rabbitmq.com/getstarted.html
  • https://openeg.co.kr/242
  • https://www.cloudamqp.com/blog/2015-09-03-part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html