본문으로 건너뛰기
김신건의 로그

[Distributed] RabbitMQ: exchange, queue, routing key

· 수정 · 📖 약 2분 · 895자/단어 #rabbitmq #amqp #message-broker #queue #backend
RabbitMQ, AMQP, exchange, routing key, direct exchange, topic exchange, fanout, DLQ, RabbitMQ Streams

정의

RabbitMQ = AMQP 0.9.1 기반 traditional message broker. exchange → queue 라우팅, workload distribution, publish-subscribe. Kafka 보다 작은 단위 메시지 라우팅에 강함.

메시지는 반드시 exchange 를 경유해 binding 규칙에 따라 queue 로 전달된다. Producer 는 queue 를 직접 알지 못하고 exchange 에만 발행한다.

언제 쓰이나

  • Task queue: 백그라운드 작업 배포 (이메일 발송, 이미지 처리)
  • Request-Reply: RPC 패턴 (reply_to + correlation_id)
  • 복잡한 라우팅: topic/headers exchange 로 세밀한 메시지 필터링
  • 마이크로서비스 이벤트: 서비스 간 비동기 통신
  • 작은 메시지, 낮은 지연: Kafka 보다 단순하고 빠른 설정

구조

flowchart LR
    P[Producer] --> Exch[Exchange]
    Exch -->|"routing key 매칭"| Q1[Queue 1]
    Exch -->|"routing key 매칭"| Q2[Queue 2]
    Exch -->|"routing key 매칭"| Q3[Queue 3]
    Q1 --> C1[Consumer 1]
    Q2 --> C2[Consumer 2]
    Q3 --> C3[Consumer 3]
컴포넌트의미
Producer메시지 발행자
Exchangerouting 규칙. 4 종류
Queue메시지 저장소
Bindingexchange ↔ queue 연결 + key
Consumer소비자

Exchange 4 종류

flowchart TB
    Direct["Direct\n(routing_key 정확 일치)"]
    Topic["Topic\n(*.error.* 와일드카드)"]
    Fanout["Fanout\n(모든 queue 로 broadcast)"]
    Headers["Headers\n(header attribute 매칭)"]

1. Direct

publish key=ORDER.PAID → queue:paid (binding key=ORDER.PAID)
publish key=ORDER.SHIPPED → queue:shipped (binding key=ORDER.SHIPPED)

하나의 exchange 에 여러 binding key 를 달아 다른 queue 로 보낼 수 있다. 같은 key 에 여러 queue 를 binding 하면 round-robin 분배.

2. Topic

publish key=order.paid.us → matches "order.*.us", "order.paid.*", "#"
와일드카드의미
*단어 1개
#0+ 단어

3. Fanout

모든 binding 된 queue 로 무조건 복사

Pub/Sub fan-out 의 정통. routing key 를 무시한다.

4. Headers

header { type: "order", region: "us" } 매칭

routing key 대신 메시지 헤더 attribute 로 라우팅. x-match: all (AND) 또는 x-match: any (OR).

실전: Python pika 로 Topic Exchange

import pika
import json

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Exchange 선언
channel.exchange_declare(exchange='orders', exchange_type='topic', durable=True)

# Queue 선언 (DLX 포함)
channel.queue_declare(
    queue='orders.paid',
    durable=True,
    arguments={
        'x-dead-letter-exchange': 'orders.dlx',
        'x-message-ttl': 60000,   # 60초 미처리 시 DLQ
        'x-max-length': 10000,
    }
)

# Binding: order.*.paid 패턴
channel.queue_bind(exchange='orders', queue='orders.paid', routing_key='order.*.paid')

# Publish (persistent 메시지)
channel.basic_publish(
    exchange='orders',
    routing_key='order.kr.paid',
    body=json.dumps({"order_id": 42, "amount": 9900}).encode(),
    properties=pika.BasicProperties(
        delivery_mode=pika.DeliveryMode.Persistent,  # 디스크 저장
        content_type='application/json',
    )
)

print("Published order.kr.paid")
connection.close()

Consumer: ack / nack

def on_message(channel, method, properties, body):
    try:
        data = json.loads(body)
        process_order(data)
        channel.basic_ack(delivery_tag=method.delivery_tag)
    except Exception as e:
        # requeue=False → DLQ 로 라우팅
        channel.basic_nack(delivery_tag=method.delivery_tag, requeue=False)

channel.basic_qos(prefetch_count=10)  # 한 번에 10개만 가져옴
channel.basic_consume(queue='orders.paid', on_message_callback=on_message)
channel.start_consuming()

메시지 보장

sequenceDiagram
    P->>Broker: publish
    Broker->>Disk: persistent? → disk write
    Broker->>P: confirm
    P->>P: confirm 받음 → 안전
    Broker->>C: deliver
    C->>C: 처리
    C->>Broker: ack
    Broker->>Broker: ack 받음 → 제거
설정의미
persistent 메시지broker 재시작 후에도 보존
durable queuebroker 재시작 후 queue 유지
publisher confirmsbroker 의 받음 확인
consumer ack처리 완료 확인
mandatory flagrouting 안 되면 unroutable 반환

IMPORTANT

모든 4가지 켜야 실제 at-least-once 보장. 하나라도 빠지면 손실 가능.

Queue 속성 (x-arguments)

속성의미예시 값
x-message-ttl큐 내 메시지 최대 대기 시간 (ms)60000 (60초)
x-max-length큐 최대 메시지 수10000
x-max-length-bytes큐 최대 바이트104857600 (100MB)
x-dead-letter-exchangeTTL/reject 시 라우팅할 DLX"orders.dlx"
x-dead-letter-routing-keyDLX 로 보낼 때 routing key"failed"
x-queue-type큐 타입"quorum", "stream"
x-priorityPriority Queue 최대 우선순위10

DLQ (Dead Letter Queue)

flowchart LR
    Q1["primary queue"] -->|"reject 또는 ttl 만료"| DLX["Dead Letter Exchange"]
    DLX --> DLQ["DLQ"]
    DLQ --> Inspect["운영자 검사"]
trigger동작
reject (requeue=false)DLQ 로
TTL 만료DLQ 로
queue 길이 한도 초과DLQ 로

자동 retry 패턴:

flowchart LR
    Q1["primary"] -->|"fail"| DLQ["DLQ"]
    DLQ -->|"ttl 5s"| Retry1["retry-1"]
    Retry1 -->|"fail"| DLQ2["DLQ 2"]
    DLQ2 -->|"ttl 30s"| Retry2["retry-2"]
    Retry2 -->|"fail"| Final["parking lot"]

Quorum Queue (HA)

RabbitMQ 3.8+ 의 고가용성 큐 타입. Raft 합의 알고리즘으로 데이터 복제.

# Quorum Queue 선언
channel.queue_declare(
    queue='orders.quorum',
    durable=True,
    arguments={'x-queue-type': 'quorum'}
)
항목Classic QueueQuorum Queue
고가용성Mirrored (구식)Raft 기반
메시지 보장브로커 장애 시 손실 가능과반 노드 생존 시 보장
성능빠름약간 느림 (consensus overhead)
권장비중요 로컬프로덕션 중요 메시지

CAUTION

Classic mirrored queue 는 RabbitMQ 3.12+ 에서 deprecated. 새 프로젝트는 Quorum Queue 를 사용하세요.

RabbitMQ vs Kafka

항목RabbitMQKafka
모델큐 + exchange 라우팅append-only log
처리량수만 ~ 수십만/s수백만/s
Latency낮음 (ms)좀 더 큼
영속옵션항상
재처리한 번 소비 후 사라짐offset 으로 임의 재생
라우팅유연 (exchange 패턴)단순 (topic + partition)
사용task queue, request-replyevent log, stream

RabbitMQ Streams (Kafka 흉내)

3.9+ 의 Stream 큐 타입. append-only log + replica + 재생 가능.

# declare 시
queue.declare("my-stream", arguments={ "x-queue-type": "stream" })

Kafka 와 비슷한 흐름. RabbitMQ 안에서 durable log + fan-out 임의 시점.

흔한 함정

WARNING

  1. persistent 없이 신뢰 = broker 재시작 시 모든 메시지 손실.
  2. prefetch 너무 큼 = 한 consumer 가 수천 메시지 lock → 다른 consumer 가 굶음. prefetch=10~100 권장.
  3. DLQ 없이 reject = 메시지 영구 손실 또는 무한 재시도.
  4. auto_ack=true = consumer 다운 시 처리 안 한 메시지가 ack 됨 (손실).
  5. Classic mirrored queue 신규 사용 = deprecated. Quorum Queue 로 전환.

관련 위키

이 글의 용어 (5개)
[Distributed] Kafka: 분산 로그, partition, consumer groupdistributed-systems
정의 Apache Kafka = 분산 commit log. 고처리량 (수백만 msg/s), 영속, 수평 확장. event-driven 아키텍처 의 de facto. 핵심 개념: …
[Distributed] NATS: 가벼움, JetStream, subject 라우팅distributed-systems
정의 NATS = 극도로 가벼운 메시징 시스템. core NATS = at-most-once Pub/Sub + request-reply. JetStream = 영속 / at-le…
[Pattern] Idempotency Keys: 중복 요청 안전 처리distributed-systems
정의 Idempotency = 같은 요청을 N번 보내도 결과가 1번과 동일. 분산 시스템 / 결제 / API 의 안전망. [!IMPORTANT] 네트워크는 항상 timeout /…
[Pattern] Outbox Pattern: DB + 메시지의 원자성distributed-systems
정의 Outbox Pattern = DB 변경 + 메시지 발행 의 원자성 보장. 이중 쓰기 (dual write) 문제 의 표준 해결. 문제: Dual Write | 시나리오 |…
[Redis] Pub/Sub vs Streams: 휘발 신호 vs 영속 로그database-internals
정의 - Pub/Sub ( / ): 지금 듣고 있는 구독자 에게만 메시지가 전달되는 휘발성 신호. 영속 없음, ACK 없음. fan-out. - Streams ( / / / ):…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기