본문 바로가기

개발/Backend

[RabbitMQ] AMQP 통신 과정 속 프레임 구조 이해하기

이번 글에서는 지난 글에 이어서 메시지를 발행하는 과정을 따라가면서 저수준에서의 구현 방식을 이해해보고자 한다.

 

✅ AMQP의 RPC 프레임 구조 이해하기

AMQP 스펙에는 클래스와 각 클래스 별 메소드가 정의되어 있다.

통신을 위해 Connection이 필요하니 Connection을 맺는 것부터 클래스 / 메소드로 알아보면서 그 과정을 따라가보자.

 

클라이언트에서는 연결을 맺을 수 있는지에 대한 데이터 덩어리를 보낸다. 여기서 말하는 데이터 덩어리를 앞으로 프레임이라고 부를 것이다. 여기서 보내진 프레임은 하나의 요청으로 간주할 수 없고, 다른 통신과 다르게 명령의 성격을 가지지 않는다.

여기서 전송되는 데이터 덩어리라는 프레임을 자세히 뜯어보자.

 

1. 프로토콜 헤더 프레임

 

  • 첫번째 자리에 있는 1은 프레임의 유형을 나타낸다. AMQP에는 총 다섯가지 유형의 프레임이 있는데, Connection을 맺기 위해 사용하는 프레임은 프로토콜 헤더 프레임이라고 부른다.
  • 두번째 자리에 있는 0은 채널 번호를 나타낸다. 단일 AMQP 연결은 여러 채널을 가질 수 있는데 이를 멀티플렉싱이라 부른다.
  • 세번째 자리에 있는 335는 프레임 본문의 크기를 나타낸다.
  • 마지막 자리에 있는 0xce는 끝을 나타내는 1바이트 마커가 있다.
프레임 유형은 위에서 살펴본 프로토콜 헤더 프레임을 포함해 총 다섯가지가 있다.

1. 프로토콜 헤더 프레임: connection을 맺을 때 한 번만 사용한다.
2. 메소드 프레임: RPC 요청이나 응답을 전달한다.
3. 콘텐츠 헤더 프레임: 메시지의 크기나 속성을 전달한다.
4. 바디 프레임: 메시지의 내용을 전달한다.
5. 하트비트 프레임: 사용 가능한 상태인지 파악하기 위해 사용한다.

 


 

 

RabbitMQ와 연결을 맺었다면 이번엔 메시지를 발행할 차례이다.

메시지를 발행한다는 것은 세 종류의 프레임을 전송한다는 의미와도 일맥상통하다.

 

가장 먼저 메소드 프레임을 전송한 후 그에 해당하는 콘텐츠 헤더 프레임을 전송한다. 그 다음 바디 프레임을 전달하는데, 각 프레임의 max size 가 있어서 하나의 프레임에 다 담지 못하는 경우 여러 프레임에 나누어서 바디 프레임을 전달하게 된다.

 

모든 프레임은 위에서 살펴본 프로토콜 헤더 프레임과 구조적으로 동일하지만, 각 프레임 별로 내부에 있는 데이터의 구조가 조금씩 상이하다. 이 과정에서 사용되는 프레임을 하나씩 살펴보고자 한다.

 

 

2. 메서드 프레임

이번에 보내는 메시지가 메시지를 발행하는 행동이라고 할 때, Basic.Publish를 사용할 수 있다.

메소드 프레임은 위 그림처럼 아래의 정보로 구성된다.

 

  • Class ID: 해당하는 값이 숫자로 전달된다.
  • Publish ID: Class ID와 마찬가지로 숫자로 전달된다.
  • Exchange name: 전달할 exchange 의 이름이 포함된다.
  • Routing key: 매칭되는 라우팅 키가 포함된다.
  • Mandatory flag: 메시지를 RabbitMQ가 라우팅해야 하는지 혹은 없는지를 확인할 수 있게 하고, 라우팅할 수 없다면 RabbitMQ는 Basic.Return 프레임을 리턴하다.

 

3. 컨텐츠 헤더 프레임

컨텐츠 헤더 프레임은 메시지의 크기나 그 외의 정보를 전달한다. 대부분의 클라이언트 라이브러리가 컨텐츠 타입이나 delivery mode 와 같은 정보를 채운다. 컨텐츠 헤더 프레임은 아래의 정보로 구성된다.

 

  • Content size: 55
  • Property flag: 144,200
  • Content type: application/json
  • App Id: Test
  • Timestamp: 101426880
  • Delivery mode: 1 (1인 경우 메시지를 발행할 때 디스크에 저장함)

 

 

4. 바디 프레임

데이터의 크기를 최소화하여 효율적으로 전송하기 위해 메소드 프레임과 콘텐츠 헤더 프레임의 내용은 바이너리로 작성되어 있지만, 바디 프레임은 인코딩되어 있지 않으며 어떤 형태로도 저장될 수도 있다. 위의 예시는 json으로 작성된 데이터이다. 

 

 

✅ 메시지 Publish 및 Consume

지금까지 메시지를 발행하기 위한 과정을 따라가며 각 프레임에 포함된 데이터 구조에 대해 이해할 수 있었다.

이번에는 실제로 메시지를 전송하기 위해 Exchange와 Queue를 생성하고 둘을 바인딩해보고, 메시지가 실제로 publish 되고 consume되는 플로우를 전체적으로 이해해보고자 한다.

 

1. Exchange 생성

 

Exchange도 Queue와 마찬가지로 AMQP 모델에서 1급 시민으로 존재한다. Exchange의 Declare 메서드를 이용해 익스체인지의 이름, 유형, 기타 메타 데이터를 함께 전송해서 익스체인지를 생성한다. 성공하는 경우에는 위와 같이 전달된다.

 

2. Queue 생성

Exchange 생성과 마찬가지로 Queue 클래스에도 Declare 메서드를 사용해서 Queue 를 생성할 수 있다.

 

3. Queue와 Exchange 바인딩

Queue 생성이나 Exchange 생성과 유사하게 Bind 메서드를 사용해서 하나의 큐를 바인딩할 수 있다. 동기 방식의 명령은 이처럼 Action에 대한 응답으로 ActionOk 패턴으로 받는 것을 확인할 수 있다. 다만 비동기로 동작하는 일부 명령은 다르게 동작하는 것에 유의해야 한다.

 

3. 메시지 Publish

하나의 메시지를 발행하는 과정에서 전송되는 프레임을 순서대로 알아보면 위와 같은 순서로 전송된다.

처음에 메서드 프레임이 전송된 후, 컨텐츠 헤더 프레임, 바디 프레임이 전송된다.

RabbitMQ가 Basic.Properties 메서드 프레임의 익스체인지와 일치하는 익스체인지를 찾은 후 내부 바인딩을 찾아서 일치하는 큐를 찾는다. 내용이 익스체인지에 연결된 큐와 일치하면 FIFO 방식으로 메시지를 큐에 삽입한다.

 

실제 메시지가 큐에 전달이 될 때에는 값이 직접적으로 추가되는 것이 아니라 참조가 추가된다. 여러 큐에 발행된 메시지는 이 참조를 통해 최적화된다. 여러 큐에 발행되는 경우에 참조만 저장하고 있기 때문에 실제 메모리를 적게 사용할 수 있게 된다.

 

4. 메시지 Consume

메시지가 발행되었다면 큐에 대기 중인 메시지를 소비해야 한다. 컨슈머는 RabbitMQ를 구독하도 연속적으로 메시지를 받을 준비를 한다.

Basic.Cancel 명령을 발행하지 않는 이상 소비하는 상태를 계속 유지하게 된다.

 

RabbbitMQ가 메시지를 보내는 동안에는 명령이 비동기적으로 실행되는 것이 주목할 점이다. Basic.CancelOk 응답을 받기 전까지는 RabbitMQ가 미리 할당한 메시지의 수만큼 계속해서 메시지를 받을 수 있다.

 

메시지를 소비할 때 컨슈머의 수신 방식을 알 수 있는 몇가지 설정이 있다. 그 중 하나가 Basic.Consume 명령의 no_ack 인데, 이에 대한 자세한 내용은 추후 다른 글에서 자세히 다룰 예정이다.

 

 

✅ 나가며

이번 글에서는 저수준에서의 AMQP의 프레임에 대해 자세히 알아봤다.

다음 글에서는 컨텐츠 헤더 프레임에서 전송되는 메시지 속성에 대해 알아보고자 한다.

그리고 그 속성이 메시지를 전달하는 데에 어떤 영향을 주는지에 대해서도 자세히 알아볼 예정이다.

 

 

✅ 참조

  • 📚 Gavin M. Roy, RabbitMQ In Depth

'개발 > Backend' 카테고리의 다른 글

authentication & authorization  (0) 2025.02.16
[RabbitMQ] RabbitMQ란  (0) 2025.01.19
모르면 손해보는 git rebase (1)  (3) 2024.10.13
HTTP/3, HTTP over QUIC  (0) 2022.07.24
프로세스(Process)와 스레드(Thread)  (0) 2021.08.15