RabbitMQ fundamentals: exchanges, bindings, and routing
A producer in RabbitMQ never actually sends a message to a queue — it sends a message to an exchange, and a separate, explicit routing configuration decides which queue (or queues) actually receive it. That extra layer of indirection is the entire point.
Intermediate
3 min read
The producer never talks to a queue directly
Unlike the simpler mental model of "producer writes to queue," RabbitMQ always inserts an exchange in between: a producer publishes a message to an exchange, and the exchange — guided by bindings set up in advance — routes a copy of that message to zero, one, or several queues. A consumer then reads from a queue exactly like the point-to-point model from lesson 1.
This indirection is deliberate: it means a producer only ever needs to know what happened ("order placed"), not who needs to know — routing logic lives entirely in the broker's configuration, not in the producer's code, so adding a new interested consumer never requires changing the producer at all.
The four exchange types, and what each actually decides
Direct: routes a message to any queue bound with a routing key that exactly matches the message's routing key — the simplest case, functionally like addressing a specific mailbox.
Topic: routes using wildcard pattern matching on a dot-separated routing key (order.created, order.cancelled.refunded) — a binding of order.* matches order.created but not order.cancelled.refunded; a binding of order.# matches both. This is the mechanism for "give me everything about orders" vs. "give me only cancellations," without the producer caring who's listening for which slice.
Fanout: ignores the routing key entirely and delivers a copy to every queue bound to it — the direct RabbitMQ equivalent of the pub/sub topic from lesson 1, useful for broadcasting to every interested service unconditionally.
Headers: routes based on message header attributes instead of the routing key — used less often, for routing decisions too complex to encode in a single string.
A worked example: topic routing for order events
Routing key published: "order.created.us-east"
Binding "order.created.#" → matches (order-fulfillment queue)
Binding "order.*.us-east" → matches (us-east-reporting queue)
Binding "order.cancelled.#" → no match (refunds queue stays empty for this message)
A single published message can match several bindings at once and get copied into every matching queue — this is genuinely different from Kafka's partition model, where a message lives in exactly one partition; in RabbitMQ, routing can fan a single message out to an arbitrary number of independently-configured queues.
Why this model trades Kafka's replay for flexible routing
RabbitMQ queues, by default, delete a message once every bound consumer has acknowledged it — there's no durable, replayable log the way Kafka retains one. What RabbitMQ trades for that is much richer, broker-side routing logic (topic wildcards, header-based rules, multiple independent queues off one exchange) that Kafka's simpler "partition by key" model doesn't attempt. Neither is strictly better: RabbitMQ fits complex routing needs with moderate throughput; Kafka fits very high throughput with replay, at the cost of routing sophistication (lesson 11 builds this into a full decision framework).