backend / message queues / rabbitmq / 01_what_is_rabbitmq.md

What is RabbitMQ

5 interview angles 4 min read source

What is RabbitMQ

A message broker — middleware that lets services communicate by passing messages instead of calling each other directly. RabbitMQ implements AMQP 0-9-1: producers publish to exchanges, which route messages into queues, from which consumers receive them. It’s the classic choice for task queues and request-style routing where you need flexible delivery, not a replayable log.

For routing depth see 03_exchanges_queues_dlx.md; for interview Q&A see 04_rabbitmq_interview.md.

The routing model

The thing that distinguishes RabbitMQ: producers never publish to a queue directly. They publish to an exchange, and bindings decide which queues receive the message.

Producer ──► Exchange ──(binding by routing key)──► Queue ──► Consumer

                  ├──► Queue B ──► Consumer 2
                  └──► Queue C ──► Consumer 3
Concept Role
Producer publishes a message (body + routing key)
Exchange receives messages, routes them to queues by rules
Binding a rule linking an exchange to a queue (often with a routing-key pattern)
Queue buffers messages until a consumer acks them
Consumer receives and acknowledges messages

Exchange types

Type Routing
direct exact routing-key match
topic wildcard match (order.*.created) — the workhorse
fanout broadcast to every bound queue (ignores routing key)
headers match on message headers instead of routing key

Minimal Python example (pika)

import pika

conn = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
ch = conn.channel()
ch.queue_declare(queue="tasks", durable=True)          # survives broker restart

# Producer
ch.basic_publish(
    exchange="",                                       # default direct exchange
    routing_key="tasks",                               # → queue named "tasks"
    body="do work",
    properties=pika.BasicProperties(delivery_mode=2),  # persistent message
)

# Consumer
def handle(ch, method, props, body):
    process(body)
    ch.basic_ack(delivery_tag=method.delivery_tag)     # ack AFTER success

ch.basic_qos(prefetch_count=1)                         # fair dispatch, one at a time
ch.basic_consume(queue="tasks", on_message_callback=handle)
ch.start_consuming()

Delivery guarantees

RabbitMQ is at-least-once when you use manual acks. The consumer acks only after successfully processing; if it crashes first, the unacked message is requeued and redelivered. That means duplicates happen — consumers must be idempotent.

Durability needs three things together, or you still lose messages:

  1. Durable queue (durable=True) — queue survives restart.
  2. Persistent messages (delivery_mode=2) — message written to disk.
  3. Manual ack — broker keeps the message until the consumer confirms.

Add publisher confirms to know the broker actually accepted a published message. Use a dead-letter exchange (DLX) for messages that fail repeatedly — see 03_exchanges_queues_dlx.md.

What it’s good for

  • Task queues / background jobs — distribute work to a pool of workers (Celery’s default broker — see ../celery/01_what_is_celery.md).
  • Complex routing — topic/headers exchanges fan one message out by rules.
  • Request/reply and per-consumer queues — work that’s consumed once, then gone.
  • Smoothing load spikes — the queue buffers bursts.

RabbitMQ vs Kafka

The interview’s favorite comparison. Different tools, not competitors.

RabbitMQ Kafka
Model smart broker, dumb consumer; routes & deletes on ack dumb broker, smart consumer; durable append-only log
After consumption message removed from queue message retained; offset advances
Replay no (it’s gone once acked) yes — rewind the offset
Routing rich (exchanges, bindings, wildcards) partitions by key; routing is the consumer’s job
Throughput high very high (designed for it)
Ordering per queue per partition
Best for task queues, complex routing, RPC event streaming, log/replay, high-volume pipelines

Rule of thumb: RabbitMQ when work is consumed once and routing matters; Kafka when you need a replayable event log at high throughput. See ../kafka/01_what_is_kafka.md.

Common pitfalls

  • Forgetting idempotency — at-least-once delivery means duplicates; non-idempotent consumers double-process.
  • Auto-ack — acking on delivery (before processing) loses messages on a crash. Ack after success.
  • Durable queue but non-persistent messages (or vice versa) — both are required to survive a restart.
  • Unbounded prefetch — one greedy consumer grabs thousands of messages; set prefetch_count for fair dispatch.
  • No DLX — poison messages requeue forever in a hot loop. Route repeated failures to a dead-letter queue.
  • Treating it like Kafka — there’s no replay; once acked, a message is gone.

Interview angle

  • “What is RabbitMQ and how does routing work?” — an AMQP message broker; producers publish to exchanges, bindings route messages into queues by routing key/pattern, consumers ack them. Decouples producers from consumers.
  • “Exchange types?” — direct (exact key), topic (wildcard), fanout (broadcast), headers (match on headers). Topic is the common one.
  • “What delivery guarantee does it give?” — at-least-once with manual acks; duplicates are possible, so consumers must be idempotent. Durability needs durable queue + persistent message + manual ack together.
  • “RabbitMQ vs Kafka?” — RabbitMQ deletes messages after consumption and excels at routing/task queues; Kafka is a retained, replayable log built for high-throughput streaming. Pick by whether you need replay and volume vs flexible routing and once-only consumption.
  • “How do you handle a message that keeps failing?” — dead-letter exchange: after N failed attempts, route it to a DLQ for inspection/retry instead of requeueing it forever.