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:
- Durable queue (
durable=True) — queue survives restart. - Persistent messages (
delivery_mode=2) — message written to disk. - 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_countfor 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.