RabbitMQ -1- AMQP 0-9-1 프로토콜
출처 : https://www.rabbitmq.com/tutorials/amqp-concepts.html#programmable-protocol
Overview
RabbitMQ는 AMQP 0-9-1 protocol을 기반으로 한 메시지 브로커이다.
AMQP 0-9-1 and the AMQP Model?
AMQP 0-9-1(Advanced Message Queuing Protocol)는 클라이언트 애플리케이션이 특정한 미들웨어 브로커와 메시지를 주고받게 하는 프로토콜을 이야기한다.
메세지 브로커는 Publisher
(메시지 송신자 혹은 producer)가 보낸 메시지를 받고, 특정한 Consumer
(메세지 수신자)에게 라우팅을 해주는 역할을 갖는다.
AMQP가 네트워크 프로토콜이라는 점을 감안하면, publisher, consumer, broker는 모두 각기 다른 machine으로 구분할 수 있다.
공식 문서를 참고하면, AMQP 모델의 구조는 다음과 같다.
- Publisher는 메시지를 작성한 후, 메세지에 브로커가 참고할 수 있고, 해당 메세지를 수신할 애플리케이션이 사용할 수 있는 다양한 meta-data를 구성한다.
- 네트워크는 통신을 실패하여, 메세지 처리를 못할 가능성이 있으므로 AMQP 모델에는 '메시지 확인'이라는 개념이 있다. 따라서, consumer는 메시지를 수신할 때, 개발자의 의도에 따라 자동 혹은 수동으로 broker에게 알림을 전송하고, 브로커는 해당 알림을 받기 전까지는 대기열 큐를 제거하지 않는다.
- 만약 작성된 메시지의 라우팅이 실패하면...
- Publisher에게 다시 돌려줄 수 있습니다.
- 그대로 버려질 수도 있습니다.
- 기능을 확장하여 애초에 작성한 메시지에 특정 파라미터를 사용하여, 'dead letter queue'라는 별도의 큐로 보내, 개별적으로 관리할 수 있습니다.
- AMQP에 사용되는 모든 큐는 바인딩과 교환 작업들을
AMPQ entity
들로 수집하여 관리한다.
AMQP 0-9-1은 프로그래밍으로 조작이 가능한 프로토콜이다.
- AMQP 0-9-1는 관리자 프로그램이 아닌, 애플리케이션 자체에 정의하여 AMQP entity, 라우팅 스키마 등을 감지하는 programmable 한 프로토콜이다. 따라서, 프로토콜 작업에 필요한 큐와 exchange 사이에 바인딩을 정의하는 작업을 프로그래머가 정의할 수 있다.
- 이 특징은 개발자에게 높은 자유도를 제공하지만, 당연하게도 오작동으로 인한 충돌 위험을 잠재적으로 가지고 있다. 하지만, 그러한 경우는 잘못된 설정을 적용하지 않는 이상, 매우 드물게 발생한다.
- 이 프로토콜을 사용하는 애플리케이션은 필요한 AMQP entity들을 지정하고, 필요 없는 entity들을 삭제할 수 있는 라우팅 스키마를 정의해야 한다.
Exchange와 Exchange Type들
Exchanges
란, 메시지의 목적지를 의미하는 일종의 AMQP entity 들이다. Exchanges
들은 메시지를 0~n개의 큐로 라우팅을 해준다.
라우팅 알고리즘은 exchange type*와 *bindings 라는 규칙에 따라 결정된다. AMQP 0-9-1 브로커에서 제공하는 _exchange type_은 다음과 같다.
Exchange type | Default pre-declared names |
---|---|
Direct exchange | (Empty string) and amq.direct |
Fanout exchange | amq.fanout |
Topic exchange | amq.topic |
Headers exchange | amq.match (and amq.headers in RabbitMQ) |
Default Exchange
이 방식의 exchange 정책은 브로커에 아무런 이름을 지정하지 않았을 때 작동된다.
단순한 애플리케이션에 유용한 하나의 프로퍼티가 적용되는데, 그 프로퍼티는 생성되고 관리되는 모든 큐에 해당 큐의 이름과 동일한 명칭의 키가 자동으로 바인딩이 된다.
Direct Exchange
direct exchange 정책은 지정된 라우팅 키를 가지고 있는 큐에게 다이렉트하게 전송하게 한다. 이 방식은 유니캐스트 라우팅 메세징에 적합한 정책이다.
Fanout exchange
라우팅 키와의 바인딩 관계를 무시하여, 모든 큐에게 메시지를 보내는 정책이다. 브로드캐스팅 라우팅에 적합하다.
Topic Exchange
1 : 1 ~ n개의 큐로 메세지를 보내기 위한 exchange 정책이다. 멀티 캐스트 라우팅 메시징에 사용된다. routing key 전체가 일치하거나 일부 패턴과 일치하는 모든 큐로 메시지가 전달된다.
Headers Exchange
이 방식은 메시지 헤더들을 여러가지 속성 값들로 기존의 라우팅 키보다 더 쉽게 표현하기 위한 메시징 교환 정책이다.
라우팅 키를 무시하고 헤더 속성을 판별하여, 구분한다.
Headers Exchanges 정책은 헤더 값들을 기반으로 라우팅을 진행하기 때문에, 문자열이 아닌, 정수나 해싱된 값들을 통해 direct exchange와 같은 메시지 교환 정책처럼 활용할 수도 있다.
Queues
AMQP 0-9-1 model에서 큐는 일반적인 메세지 기반의 task-queueing 시스템과 거의 동일하다. 즉 애플리케이션에 의해 메시지들을 쌓고 소비하는 형태의 큐를 관리한다고 생각하면 된다.
만약에 사용하고자 하는 큐가 아직 존재하지 않은 상태에서 선언된다면, 해당 큐가 생성되는 과정을 갖게 될 것이다. 만약 지정한 속성 값과 동일한 큐가 이미 있는 상태에서 선언된다면, 아무런 효과를 가지지 않을 것이다. 그러나, 만약 선언하고자 한 큐의 속성값과 이미 생성된 큐의 속성 값이 일치하지 않는다면, 채널 레벨에서 406(PRECONDITION_FAILED) 에러가 발생할 것이다.
Queue Names
애플리케이션은 큐의 이름을 고르거나, 브로커에게 이름을 생성하도록 요청할 수 있다.
큐의 이름은 브로커 내부 규칙에 의해 'amq.'으로 시작하도록 예약된다. 만약 이 규칙을 어기게 된다면, 채널 레벨에서 403 에러(ACCESS_REFUSED)를 리턴한다.
Queue Durability
AMQP 0-9-1에서 큐는 durable(거의 영구적) or transient(일시적)으로 선언될 수 있다. durable 큐의 메타데이터는 디스크에 저장되고 후자는 메모리에 저장된다(가능하다면).'
Bindings
_Bindings_는 _Exchanges_가 목적지 큐에게 메시지를 라우팅 하게 하는 일종의 규칙이다. _Bindings_는 몇몇 exchanges 정책에 따라 부가적인 라우팅 키를 가질 수 있다. 라우팅 키의 존재 이유는 exchanges와 바인딩되어 있는 큐로 작성된 메시지를 전송하기 위함이다.
만약 메시지가 어떠한 큐에도 전송되지 못한다면, publisher에 설정한 메세지 속성 값에 따라, publisher에게 돌아가거나 버려진다.
Consumers
AMQP 0-9-1 Model은 메시지를 큐에 쌓고, 소비하는 행위를 지속하는데, 그 형태는 다음과 같다.
- push API처럼 메시지를 구독하는 형태.(일반적으로 추천하는 형태)
- Polling("pull API"): 공식적으로 지양하는 방식
consumer는 각각 _consumer tag_라는 단순한 문자열로 이루어진 식별자를 가지고 있으며, 메세지를 통해 해당 식별자가 해지될 수 있다.
Message Acknowledgents
Consumer application과 같이 메세지를 받고 진행하는 작업은 때때로 크고 작은 충돌을 일으킬 수 있다. 따라서, 잠재적인 네트워크 충돌 문제가 있을 수 있다.
AMQP 0-9-1 명세서에는 두 가지 형태의 Consumer Acknowledgement 모드를 지원한다.
- 브로커가 애플리케이션에 메시지를 보낸 후에 ack 신호를 주는 모드 (
basic.deliver
orbasic.get-ok
method) - 애플리케이션이 ack 신호를 돌려준 이후에 작동하는 모드 (
basic.ack
method)
전자의 경우, automatic acknowledgement model 이라 하고, 후자의 경우 explicit acknowledgement model 이라고 한다. 후자의 모델을 채택한 애플리케이션은 언제 ack 신호를 보낼지 결정한다. 메시지를 수신한 이후에 보낼수도 있고, 데이터 저장소에 저장되기 직전에 보낼수도 있다. 혹은 전부 다 진행되고나서, 보낼수도 있다.
만약 컨슈머가 ack 메세지 없이 죽는다면, 브로커는 다른 컨슈머에게 재전송하는데, 만약 재전송할 큐도 없다면, 브로커는 하나 이상의 컨슈머가 같은 큐로 등록되는 걸 확인하기 전까지는 계속 대기한다.
Rejecting Messages
애플리케이션은 브로커에게 메시지 수신을 거부함으로서, 실패처리(혹은 해당 시간엔 진행할 수 없는 상태처리)를 할 수 있다. 메세지를 거부했을 때, 애플리케이션은 브로커에게 메세지를 버리거나 다시 대기열에 올리는 행위를 요청할 수 있다. 만약 단 하나의 큐를 관리할 경우, 같은 컨슈머가 메세지를 거절하고 재대기열 배열을 하는 행위를 무한반복하는 행위를 하지 않도록 조심해야 한다.
Negetive Acknowledgements
메세지 수신 거절을 basic.reject
메소드에 의해 결정된다. 메소드 거절 메소드는 이 하나밖에 없기 때문에 여러 메시지들을 한 번에 거절하는 방법은 없다. 그러나 만약 RabbitMQ를 사용한다면, negative acknowledgements or nacks.라는 AMQP 0-9-1 모델로 알려진 확장 라이브러리를 제공받을 수 있다.
Prefetching Messages'
하나의 큐를 공유하는 여러 컨슈머의 경우, 각 컨슈머가 순차적으로 하나의 메시지로 각각 전송되는 구조에서는 효과적일 수 있다. 이것은 대표적으로, 간단한 분산 부하 기술 혹은 배치로 작성되는 메시지들의 처리량을 개선하는데 유리할 수 있다.
단 기억해둬야 할 부분은 RabbitMQ는 오직 채널 레벨에서 셀 수 있는 번호를 출력할 수 있다.
Message Attributes and Payload
- Content type
- Content encoding
- Routing key
- Delivery mode (persistent or not)
- Message priority
- Message publishing timestamp
- Expiration period
- Publisher application id
메시지 속성 값들은 AMQP 브로커에서 사용되지만, 대부분 그 메시지들을 받는 애플리케이션에서 해석이 가능할 정도로 오픈되어있다.
메시지는 또한 payload(데이터가 가지고 있는 내용)를 가졌는데, AMQP 브로커는 이걸 불투명한 바이트 배열로 취급한다. 따라서 브로커는 해당 페이로드를 조사하거나, 수정할 수 없다.
속성 값들은 종종 페이로드 없이 오직 몇몇 속성 값들만을 포함할 수 있는데, 이러한 경우 주로 JSON과 같은 특정 포맷으로 직렬화를 하는 데 사용될 수 있다.
메시지들은 브로커가 디스크로 영속화시킬 수 있다. 따라서 경우에 따라 서버가 재시작 되더라도 시스템으로부터 해당 메세지를 유실되지 않은 상태로 수신할 수 있다.
Message Acknowledgements
네트워크가 항상 통신을 성공하는 것은 아니기 때문에 ack 과정이 필요하다. 따라서 AMQP 0-9-1는 _message acknowledgements or acks_라는 이미 만들어진 ack 작업을 가지고 있어서, 메세지 전송 혹은 전송 처리 중에 신호를 관리하는 기능을 가져서 개발자가 좀 더 튼튼한 소프트웨어를 빌드할 수 있는 환경을 제공한다.
AMQP 0-9-1 Methods
- exchange.declare
- 몇몇 파라미터를 가지고, 브로커에게 큐 선언 요청하는 메소드
- exchange.declare-ok
- 큐 선언이 제대로 완료되었음을 클라이언트에게 알리는 메소드
-
- exchange.delete
- 큐 삭제 요청
- exchange.delete-ok
- 큐 삭제 완료 응답
보통 AMQP 0-9-1 큐에서 요청/응답은 다음과 같이 순차적으로 처리된다.
)
모든 프로토콜 메시지가 응답을 하는 것은 아니다. (basic.publish
와 같이)
해당 프로토콜 메소드에 대한 상세한 내용을 알고 싶다면 여기를 참고하자
https://www.rabbitmq.com/amqp-0-9-1-reference.html
Connections
AMQP 0-9-1 커넥션의 수명은 일반적으로 길다. TCP 기반으로 연결 지향 통신이 가능하고, TLS에 의해 보호받을 수도 있다. 만약 해당 애플리케이션을 더 이상 사용하지 않는다면, 꼭 종료를 먼저 해야 한다.
Channels
몇몇 애플리케이션은 브로커와의 여러 개의 커넥션이 필요할 수 있다. TCP 커넥션을 여러 개를 동시에 형성하는 것은 리소스 관리 측면에서 긍정적이지 못한 선택이지만, *channels*를 통해 하나의 TCP 커넥션으로 멀티 플렉싱 통신을 하여 좀 더 경량화된 멀티 커넥션을 형성할 수 있다.