Rabbit MQ에 관하여

RabbitMQ를 학습하고 정리한 글입니다.

RabbitMQ

AMQP를 구현한 메시지 브로커

AMQP

AMQP(Advanced Message Queing Protocol)는 오픈 소스 기반 MQ 표준 프로토콜을 얘기한다. 효율적 메세지 전송을 위한 MQ 구성요소 및 메세지 포맷 등을 정의한 것이다.

특징

  • ISO 표준(ISO/IEC 19464) AMQP 구현
  • 비동기처리를 위한 메세지큐 브로커
  • erlang과 java 언어로 구현됨
  • 분산처리를 고려한 MQ(Cluster, Federation)
  • 고가용성 보장(HA)
  • Publish/Subscribe 방식 지원
  • 다양한 plugin 지원

AMQP 구성요소

AMQP 구성요소 (출처)

Queue

메세지를 저장하는 디스크/메모리 상 자료구조이다.

durable 속성을 통해 메세지를 디스크에 저장할 수 있다. 메모리에 저장하는 것은 transient라고한다. transient로 메모리에 저장하더라도 메모리가 부족해지면 메모리에 있는 메세지를 디스크에 저장한다.

auto-delete를 통해서 모든 컨슈머가 unsubscribe를 하면 queue는 자동으로 없어지도록 설정이 가능하다.

Exchange

메세지 브로커에서 큐에 메세지를 전달하는 컴포넌트이다.

메세지 데이터/속성을 분석하여 메세지에 라우팅 동작을 정의할 수 있으며 RabbitMQ 내에 서로다른 라우팅 동작을 처리하는 유형의 Exchange가 존재한다. 또한, 플러그인을 통해 커스텀 Exchange를 정의하는 것도 가능하다고 한다.

Exchange Type으로 4가지가 있다.

  • Direct Exchange

    각 queue는 routing key에 binding이 되어 있고, exchange에 routing key가 들어오면 해당 exchange에 binding되어 있는 queue중에서 key와 맵핑되어 있는 queue로 메세지를 라우팅하는 방식이다.

  • Fan out Exchange

    routing key와 상관없이 exchange에 binding되어 있는 모든 queue에 메시지를 라우팅하는 방식이다.

  • Topic Exchange

    exchange에 맵핑되어있는 queue중에서 routing key가 패턴에 맞는 queue로 모두 메세지를 라우팅한다.

  • Header Exchange

    메시지 Header에 있는 attribute를 이용해서 queue에 메시지를 라우팅하는 방식이다.

Binding

queue와 Exchange를 연결하는 것을 말한다.

Connection

물리적인 TCP Connection을 말하며 보안이 필요할 경우 TLS(SSL) Connection을 사용할 수 있다.

Channel

하나의 물리적인 connection 내에 생성되는 가상 논리적인 connection들이다.

하나의 connection을 활용하여 tcp의 3-hand shake를 매번하지 않기위한 개념이다. 따라서 consumer의 process나 thread는 각자 이 channel을 통해 queue에 연결될 수 있다.

Message Publish

Message Publish (출처)

AMQP 명령은 class와 method로 구성된다.

예를 들어, Connection.Start 명령은 Connection class와 Start method로 구성된 것이다.

Message Consume

Message Consume (출처)

Consumer Tag

Consumer 애플리케이션을 식별하는 고유한 문자열이다. Basic.Consume 명령 실행시 생성된다. 메시지 수신 작업을 취소할 때 사용할 수 있다.

Basic.Consume vs Basic.Get

메시지 소비를 하는 방식은 두 가지 방식이 있는데, Basic.Consume과 Basic.Get 방식이다.

Basic.Consume은 push 기반 메시지 소비 모델이며 Basic.Get에 대비 2배정도 빠르다고한다. 또한 비동기로 소비자 애플리케이션에 메시지를 전달하는 방식이다. 비동기로 메시지를 push한다.

Basic.Consume은 prefetch를 사용할 수 있다. prefetch는 한번에 정해진 양의 메시지를 소비자가 fetch할 수 있도록 하는 것이다. prefetch를 통해 메시지를 한번에 받고 메시지 소비완료인 ack를 하나씩 브로커에게 전달하면 브로커는 ack받은 메시지 하나를 제거하고 큐에 메시지가 있으면 메시지 하나를 보내주게된다. 이로써 소비자는 많은 메시지를 미리 들고 처리를 하는 방식이다.

Basic.Get은 연결이후 소비자가 직접 메시지를 polling하는 방식이다. 따라서 항상 소비자가 직접 브로커에게 메시지를 요청하게 된다. Basic.Get은 브로커와 소비자 사이의 동기화되어 동작한다.

Automatic acknowledgement mode and Manual acknowledgement mode

Automatic acknowledgement mode(auto ack)와 Manual acknowledgement mode가 있는데, 후자이면 RabbitMQ는 Basic.ack를 받게되면 그때 해당 메시지를 지운다. 하지만 성능을 더 올리기위해 auto ack를 설정할 수 있고 메시지를 보내면 바로 메시지를 지우는 방식으로 동작한다.

auto ack 방식은 메세지를 성공적으로 배달하기전, tcp 커넥션이 끊기게 됐을때 메시지 손실이 잃어날 수 있다. 또한 Manual acknowledgement(not auto ack)를 통해서 했을 때에는 prefetch를 통해 소비자의 속도에 맞추어 메시지를 전달 할 수 있게되는데, auto ack 방식으로 하게되면서 제한이 없어지고 소비자에게 압박을 가할 수 있게된다.

Basic.nack and Basic.reject

Basic.ack 이외에 Basic.nack, Basic.reject가 있다. nack과 reject은 메시지를 처리하지 못한다는 응답이다.(즉, 둘 다 not ack 이다.) 부정적인 응답을 했을 때, requeue 필드가 true라면 큐에 맨앞 또는 원래있는 위치에 requeue된다. default 설정으로는 nack는 requeue가 true이고 nack은 false이다.

nack과 reject의 다른 차이는 nack은 requeue 파라미터 뿐만이 아니라 multiple parameter도 갖고있다. 따라서 여러 메시지를 한번에 not ack할 수 있다.

dead-letter queue

requeue하는 것이 아니라 메시지를 일시적으로 다른 곳에 보관해두기 위해 다른 큐에 전달할 수 가 있다. 이 큐를 dead-letter라고 한다.

  • requeue=falsebasic.rejectbasic.nack 처리되는 경우
  • queue에서 메시지 TTL이 다 된 경우 (expire)
  • queue가 가득차서 넘치는 경우 (x-max-length)

위 3가지 경우 dead-letter 큐로 넘길 수가 있다.

basic.nack과 basic.reject를 사용할때 필수적으로 requeue=false로 전달해야하며, auto ack 또한 false로 설정해야된다. (또한, nack을 브로커에게 전달할때 x-dead-letter-exchange를 지정해야한다.)

AMQP Frame

AMQP 모든 명령은 프레임 형태로 전송된다.

Frame (출처)

프레임 유형

  • Protocol Header Frame

    RabbitMQ 연결시 한번 사용되는 프레임

  • Method Frame

    RPC 요청이나 응답을 의미

  • Content Header Frame

    메세지 크기와 속성을 포함하는 프레임

  • Body Frame

    메시지 내용을 포함하는 프레임

  • Heartbeat Frame

    클라이언트 / 서버의 heartbeat 확인 용도

채널번호

하나의 AMQP connection에서 여러개의 channel을 처리해서 사용하기 위한 부분

하나의 connection에서 많은 channel을 처리하면 할수록 메모리 사용률이 증가한다.

프레임크기

최대 Frame 크기 : 31bits 로 표현가능한 범위

Message Format

RabbitMQ 메세지 발행은 Method Frame (Basic.Publish) + Content Header Frame + Body Frame (1개 이상) 으로 구성된다.

Message Format (출처)

Message Format: basic.properties (출처)

HA(High Availability)

Cluster (출처)

Rabbit MQ는 클러스터를 구성할 수 있다. 이 노드들은 보안을 위해 erlang의 cookie를 사용하여 클러스터와 통신한다. RabbitMQ 브로커의 동작을 위해 필요로 하는 모든 데이터와 상태는 모든 노드에 걸쳐 복제된다. 이것에 대한 한가지 예외가 메시지 큐인데 메세지 큐는 기본적으로 복제되지않는다. 메시지 큐는 기본적으로 하나의 노드에 머물지만, 모든 노드로부터 보여지고 도달 가능하다.

Client는 Cluster의 RabbitMQ중에서 하나의 RabbitMQ와 Connection을 맺지만, HA를 위해서 Client는 Cluster의 모든 RabbitMQ와 Connection을 맺을 수 있는 환경이어야 한다. 그래야 Cluster의 특정 RabbitMQ가 죽을경우 Client는 다른 RabbitMQ와 Connection을 맺어 계속 RabbitMQ를 이용 할 수 있기 때문이다. 일반적으로 Client와 RabbitMQ Cluster 사이에 Load Balancer를 두어, Client에게 Cluster의 모든 RabbitMQ에 접속할 수 있는 환경 제공 및 Connection Load Balancing을 수행한다. 또는 Client가 Cluster의 모든 RabbitMQ의 IP(Domain), Port 접속 정보 갖고 있어, Client 스스로 RabbitMQ 장애 감지 및 Connection Load Balancing을 수행하는 방법을 이용한다.

Cluster를 구성하는 각 RabbitMQ는 Disk, RAM 2가지 Mode를 이용할 수 있다. Disk Mode는 Default Mode이다. RAM Mode는 Message, Message Index, Queue Index, 다른 RebbitMQ의 상태 정보를 제외한 나머지 모든 정보를 Memory (RAM)에만 저장하고 구동하는 Mode이다. RAM Mode에서도 Message와 관련된 정보는 여전히 Disk에 저장되기 때문에 RAM Mode를 이용해도 Message 처리량은 증가하지 않는다. 하지만 Exchange, Queue, Binding 등의 정보가 굉장히 많고 설정이 자주 변경되는 환경에서는 RAM Mode를 이용하여 빠르게 설정을 변경 할 수 있다. Cluster 구성시 반드시 하나 이상의 RabbitMQ는 반드시 Disk Mode로 동작시켜야 한다. RAM Mode의 RabbitMQ는 재시작시 Disk Mode가 갖고 있는 RabbitMQ의 정보를 받아서 초기화하기 때문이다.

Cluster With Mirroring (출처)

특정 큐를 갖고 있는 노드가 죽는다면 그 노드에 있는 메시지는 노드가 살아나기 전까지 사용하지 못하게된다. 이를 해결하기 위해 미러링(메시지 큐 복제)을 해야한다. 복제된 큐는 하나의 마스터와 하나 이상의 슬레이블 구성이 되게된다. 만약 어떤 이유로 마스터가 사라지면 가장 오래된 슬레이브가 새로운 마스터로 승격이 된다.

큐에 게시된 메시지는 모드 슬레이브로 복사가 되고 컨슈머는 자신이 어느 노드에 연결되어있는지와 상관없이 마스터 큐로 연결된다. 따라서 큐 미러링은 그대로 가용성을 향상시키지만 노드에 걸쳐 부하를 분산시키지는 않는다.

노드를 추가하여 어떠한 큐의 슬레이브를 추가했다고 했을 때, 곧바로 동기화되지않는다. 슬레이브를 추가한 이후, 마스터 큐에 들어오는 메시지는 슬레이브로 전달되지만 이전에 있던 메시지는 전달되지않는다. 따라서 마스터 큐가 이전에 있던 메시지를 다 소비했을 때 자연스레 동기화가 되는 것이다.

Reference

AMQP

Persistence Configuration

Consumer Acknowledgements and Publisher Confirms

Clustering Guide

Connections

channels

Basic.get vs Basic.Consume

Dead-Letter

HA

⤧  Previous post In-Memory DataBase Redis