Programming/Message Queue

RabbitMQ 개념과 구조 정리 (Exchange Type, Dispatch 등)

Jan92 2023. 10. 12. 23:04

RabbitMQ 개념과 구조 정리

RabbitMQ 개념 및 구조 정리

RabbitMQ란?

'RabbitMQ'는 AMQP를 구현하여 메시지 생성자와 소비자 사이에서 메시지를 중계해 주는 '메시지 브로커'입니다.

* AMQP(Advenced Message Queuing Protocol) 메시지 지향 미들웨어를 위한 개방형 표준 응용 계층 프로토콜

 

RabbitMQ는 메시지 지향 미들웨어(Message Oriented Middleware)에 속하는데요.

메시지 지향 미들웨어는 비동기 메시지를 사용하는 응용프로그램들 사이에서 데이터를 송수신하는 것을 의미하며, 이러한 시스템을 구현한 솔루션을 '메시지 큐(Message Queue)'라고 합니다.

 

그렇다면 메시지 큐를 사용하는 이유는 무엇일까요? 메시지 큐를 사용함으로써 얻을 수 있는 이점은 아래와 같습니다.

 

 

1. 비동기(Asynchronous)

메시지 큐의 경우 생산된 메시지의 저장, 전송에 대해 동기화 처리를 하지 않고 Queue에 넣어두기 때문에 나중에 처리할 수 있으며, 이러한 방식으로 인해 기존의 동기화 방식에서 발생할 수 있는 병목 현상을 방지할 수 있습니다.

 

2. 낮은 결합도(Decoupling)

메시지를 생산하여 발송하는 서비스(Producer)와 메시지를 받아서 처리하는 서비스(Consumer)가 독립적으로 행동하면서 서비스 간의 결합도가 낮아지는 장점이 있습니다.

 

3. 확장성(Scalable)

다수의 프로세스들이 메시지 큐를 통해 메시지를 보낼 수 있기 때문에 확장성 및 분산 처리에 대한 장점이 있습니다.

 

4. 탄력성(Resilience)

메시지를 받아서 처리하는 서비스가 다운(중지) 되더라도 메시지 큐가 중단되는 것은 아니기 때문에 메시지는 Queue에 남아있게 되며, 중지된 서비스가 재시작되면 Queue에 있는 메시지를 다시 처리할 수 있습니다.

 

5. 보장성(Guarantees)

메시지 큐는 보관되는 모든 메시지가 결국 Consumer 서비스에게 전달된다는 일반적인 보장을 제공합니다.

 

 

+++

추가로 '메시지 브로커'의 경우 메시지를 받아서 처리하고 나면 즉시 또는 짧은 시간 내에 메시지가 삭제되는 구조이며 'RabbitMQ''Redis'가 메시지 브로커에 해당됩니다.

반면 '이벤트 브로커'의 경우 발생한 이벤트를 보존할 수 있다는 특징이 있는데요. 'Kafka'는 이벤트 브로커에 해당됩니다.

 

 


RabbitMQ 구조

RabbitMQ 구조

(이미지 출처 - https://www.cloudamqp.com/blog/part1-rabbitmq-for-beginners-what-is-rabbitmq.html)

 

다시 RabbitMQ로 돌아와서 'RabbitMQ의 구조'에 대해 살펴보겠습니다.

RabbitMQ의 주요 구성 요소는 'Producer', 'Consumer', 'Exchange', 'Binding', 'Queue'로 이루어져 있는데요.

이어지는 내용을 통해 하나씩 살펴보겠습니다.

 

 

- Producer

메시지를 생성하고 발송하는 주체입니다. 아래 Exchange를 통해 자세하게 살펴볼 테지만 Producer는 메시지를 Queue에 직접 보내는 것이 아니라 Exchage에 Publish 한다는 특징이 있습니다.

 

- Consumer

메시지를 받아서 처리하는 주체입니다.

 

- Exchange

Producer로부터 전달받은 메시지를 어떤 Queue에 발송할지 결정하는 객체로 4가지 타입(Direct, Topic, Headers, Fanout)이 있으며, 각각의 타입과 Binding 규칙에 따라 적절한 Queue로 메시지를 전달합니다.

(각각의 Exchange Type에 대한 자세한 내용은 아래 예시와 함께 더 자세하게 살펴보겠습니다.)

 

- Binding

Exchange와 Queue를 연결하는 관계입니다.

Exchante Type과 Binding 규칙에 따라 적절한 Queue로 메시지가 전달됩니다.

 

- Queue

RabbitMQ 안에서 메시지를 일시적으로 저장하는 장소이며, Consumer들은 Queue에 저장된 메시지를 읽어 들입니다.

Queue는 RabbitMQ 서버가 설치되는 호스트의 디스크 용량 및 메모리에 한정된다는 특징이 있습니다.

 

 


Exchange Type

아래 내용을 통해 Direct, Topic, Headers, Fanout 4가지의 Exchange Type에 대해 자세하게 살펴보겠습니다.

 

 

- Direct Exchange

RabbitMQ Direct Exchange

메시지에 포함된 routing key를 기반으로 Queue에 메시지를 전달합니다.

이미지와 같이 하나의 Queue에 여러 routing key를 지정할 수 있고, 여러 Queue에 같은 routing key를 지정할 수도 있습니다.

 

Default Exchange는 이름이 없는 Direct Exchange이며, RabbitMQ에서 생성되는 Queue가 자동으로 binding 되는데, 이때 각 Queue의 이름이 routing key로 지정됩니다.

 

 

 

- Topic Exchange

RabbitMQ Topic Exchange

메시지에 포함된 routing key의 패턴을 이용하여 메시지를 라우팅 하는 방식으로, 키 전체가 일치하거나 일부 패턴과 일치하는 모든 Queue로 메시지가 전달됩니다.

여러 Consumer에서 메시지 형태에 따라 선택적으로 수신해야 하는 경우와 같이 다양한 publish/subscribe 패턴 구현에 활용될 수 있습니다.

 

Topic Exchange에서 사용되는 binding key는 점(.)으로 구분되는 단어의 조합으로 정의되는데요.

(*) 또는 (#)을 사용할 수 있으며, (*)는 정확히 하나의 단어를 대체하고 (#)은 0개 혹은 여러 개의 단어를 대체합니다.

(Direct Exchange에서는 '#' 또는 '*' 같은 와일드카드 기능을 지원하지 않습니다.)

 

위 이미지와 같이 binding key를 정의한 경우 예시를 살펴보면 아래와 같습니다.

 

메시지의 routing key가 'quick.black.cat'인 경우 Q1과 Q2 둘 다 전달되며, 'lazy.white.cat'의 경우 Q2의 바인딩 키 2개와 일치하지만 한 번만 전달됩니다.

마지막으로 'quick.yellow.duck'의 경우 일치하는 binding key가 없기 때문에 전송되지 않게 됩니다.

 

 

 

- Headers Exchange

메시지 라우팅을 위해 header를 사용하는 방식입니다. binding key만을 사용하는 것보다 더 다양한 속성을 사용할 수 있다는 특징이 있습니다.

Header Exchange를 사용하면 routing key 속성은 무시되고 headers 속성의 값이 바인딩 시 지정된 값과 같은 경우에 일치하는 것으로 간주됩니다.

 

Header Exchange는 x-match 값에 따라 동작 방식에 차이가 있는데요.

'x-match = any'의 경우 헤더 테이블의 값(key-value 쌍) 중 하나가 연결된 값 중 하나와 일치하면 메시지를 전달하며, 'x-match = all'인 경우 헤더 테이블의 값(key-value 쌍)과 모든 값이 일치해야 메시지를 전달합니다.

 

 

 

- Fanout Exchange

routing key에 관계없이 Exchange에 등록된 모든 Queue에 동일한 메시지를 전달하는 브로드캐스트 라우팅 방식으로 동작합니다.

* 브로드캐스팅(broadcasting)은 송신 호스트가 전송한 데이터가 모든 호스트에게 전송되는 방식을 의미

 

 


Round-Robin Dispatch, Fair Dispatch

만약 하나의 Queue에 여러 개의 Consumer가 붙어있다면 RabbitMQ에서는 Round-Robin 방식을 통해 Consumer에게 메시지를 균등하게 분배합니다.

(해당 방식을 통해 메시지를 병렬적으로 처리할 수 있습니다.)

 

RabbitMQ 'prefetch_count = 1'

하지만 Round-Robin 방식이 무조건 효율적일 거라고는 볼 수 없는데요.

 

예를 들어 하나의 Queue에 두 개의 Consumer가 존재하고, 이 두 개의 Consumer에는 Round-Robin 방식으로 메시지가 분배됩니다.

이때, 만약 홀수 번째의 메시지는 처리 시간이 매우 길고 짝수 번째 메시지는 처리 시간이 짧은 경우, 홀수 번째 메시지를 처리하는 Consumer에는 처리해야 할 메시지가 계속 누적되는 경우가 발생할 수 있는데요.

 

이러한 상황을 예방하기 위해서 RabbitMQ에서는 'Prefetch'를 설정할 수 있습니다.

Prefetch는 Queue의 메시지를 Consumer의 메모리에 쌓아둘 수 있는 최대 메시지의 양으로 Prefetch Count의 값을 1로 설정하면 하나의 메시지가 처리되기 전에는 새로운 메시지를 받지 않기 때문에 작업을 분산시킬 수 있습니다.

 

 


여기까지 메시지 브로커 RabbitMQ의 개념과 구조에 대해 정리해 보았는데요.

이어지는 포스팅을 통해 Spring Boot에서 RabbitMQ를 연동하여 사용하는 코드에 대해서도 살펴보도록 하겠습니다.

 

내용 중 잘못된 부분은 댓글 남겨주시면 확인하여 수정하겠습니다.

감사합니다.

 

 

< 참고 자료 >

https://velog.io/@sdb016/RabbitMQ-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90

https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/#gsc.tab=0