RabbitMQ works by routing messages through exchanges into queues. A producer publishes to an exchange with a routing key, bindings decide which queues receive a copy, the broker pushes messages to subscribed consumers, and each message is deleted once its consumer acknowledges it.
Architectural differences: the core mental model
Kafka runs a dumb-broker, smart-consumer model. The broker appends records to a partitioned log and serves fetches. Consumers carry the intelligence, tracking their own offset per partition. That model, end to end, is mapped in the complete Kafka guide.
RabbitMQ inverts that. The broker is the smart party: it holds the routing rules, tracks which message has been delivered to which consumer, and waits for an acknowledgement per message before removing it (AMQP 0-9-1 model). A queue is a transient holding structure, not a durable history (what RabbitMQ is covers the system’s full surface). The Kafka-side equivalent of this per-message state simply does not exist, because an offset is the only consumer state Kafka keeps.
Message routing and filtering
In Kafka, a producer picks a topic and optionally a partition key, and that is the whole routing story unless a custom Partitioner is supplied. In RabbitMQ, producers never publish to a queue directly. They publish to an exchange, and the exchange type decides distribution (AMQP 0-9-1 model):
The RabbitMQ Exchanges tab with its bindings, showing direct, fanout, topic and headers types.
- Direct: routing key must match the binding key exactly.
- Fanout: every bound queue gets a copy, the routing key is ignored.
- Topic: wildcard match between routing key and binding pattern.
- Headers: match on message attributes instead of the routing key.
Dynamic filtering that would need thousands of Kafka partitions, or a stream-processing layer, is a binding change in RabbitMQ.
Consumption and consumer state
Kafka consumers commit an offset, a single pointer per partition, and can rewind it to replay anything still retained. RabbitMQ tracks state per message through acknowledgements. Two modes exist: automatic acknowledgement on delivery, or explicit acknowledgement after the application has processed the message (AMQP 0-9-1 model). Once acknowledged, the message is removed. There is no rewind on a queue.
The replay exception is RabbitMQ streams, an append-only log with non-destructive reads where consumers attach at any point and re-read until retention expires (RabbitMQ streams). Kafka’s mirror of RabbitMQ’s per-message acknowledgement is share groups from KIP-932, which introduce record-level acknowledgement on Kafka 4.0 or newer, covered in Queues for Kafka explained.
The offset model confuses people coming from per-message acknowledgement, and it shows up in support conversations constantly. In most of the lost message complaints I have worked, the message was actually sitting on Kafka the whole time. The consumer’s offset had moved past it, or the group started from latest, and from the application’s seat that is indistinguishable from data loss. The lesson for anyone holding the RabbitMQ mental model: on Kafka, delivery state is your offset arithmetic, so the first debugging step is to inspect offsets and the data on the topic, not to assume the broker dropped anything. Inspecting a topic’s data directly is the fastest way to settle it.
Concurrency and scaling
Kafka parallelism is capped by partitions: one consumer per partition per group, so a three-partition topic supports three active consumers in a group and a single-partition topic supports one. RabbitMQ supports competing consumers natively. Any number of workers subscribe to the same queue, the broker distributes messages among them, and the prefetch (QoS) setting bounds how many unacknowledged messages each worker holds (AMQP 0-9-1 model). Per-message state tracking is the scaling cost the broker pays for that flexibility.
Partition-capped parallelism sounds abstract until you meet it at scale. In my talk, Kafka operational issues and how to survive them, I walked through a service that was consuming from hundreds of topics, each with around 10 partitions, and had been scaled to hundreds of instances. Most of those instances were doing nothing, because the partition math capped the useful worker count far below the deployed one. The lesson we took from it: on Kafka you plan parallelism at topic design time, where a RabbitMQ team plans it at deploy time by adding workers. Neither is wrong, but they are different disciplines, and mixing them up wastes real money.
Durability and high availability
Kafka replicates each partition across brokers with a leader and followers, and durability is a configuration outcome: replication factor, producer acks, and in-sync replica settings. How the two systems score criterion by criterion is on the difference between Kafka and RabbitMQ.
RabbitMQ’s replicated queue type is the quorum queue, “a durable, replicated queue based on the Raft consensus algorithm.” Quorum queues are always durable, persist to disk before processing, default to a group of three members, one per cluster node, and require a majority of members online to operate. Classic queue mirroring, the older mechanism, was removed in RabbitMQ 4.0 (RabbitMQ quorum queues). A team that evaluated RabbitMQ replication in the mirrored-queue era is looking at a retired design.
Replication designs only prove themselves when hardware dies, so judge both systems on that day, not on the diagram. One case from my operational-issues talk: a cluster ran quietly for six months, then a single disk failed on one broker and Kafka kicked off exactly the rebuild it was designed to do. The recovery traffic itself became the event to manage. The lesson we took from it: durability is close to a guarantee in either system when the replication settings are right, and the operational question is whether you have sized and observed the cluster for the day it exercises them.
Why Kafka teams reach for RabbitMQ
The common reasons a Kafka team reaches for RabbitMQ are the features Kafka does not carry natively (the family-level fit decision is on Kafka vs other brokers):
- Protocols. AMQP 0-9-1 and 1.0, MQTT, and STOMP on their standard ports (RabbitMQ networking), for devices and legacy systems that will never speak the Kafka wire protocol.
- Delayed and per-message expiry. TTL per queue via
x-message-ttl, TTL per message via theexpirationproperty, lower value wins (RabbitMQ TTL). - Broker-level dead lettering. Dead-letter routing is a broker feature in RabbitMQ, where in Kafka a DLQ is a pattern the consumer implements, as covered in Dead letter queues in Kafka.
- Fine-grained routing without partitions. Dynamic per-attribute delivery through exchanges rather than topic proliferation.
On dead lettering I will add the design opinion behind the pattern, because broker-level DLQs make it easy to skip the thinking. In many real event-driven systems a topic has multiple independent consumers, and replaying from a shared DLQ, or back to the original topic, causes unintended reprocessing by consumers that never failed. The pattern I recommend is a dedicated retry stream per topic and consumer pair, with the DLQ reserved as the give-up destination. And whichever broker you run, the steady-state goal for a DLQ is effectively zero messages. Every entry is a failure signal you are now paying an operational tax to unwind.
FAQ
Is RabbitMQ push or pull?
Push, mechanically. The broker delivers messages to subscribed consumers as they arrive, and the prefetch (QoS) setting bounds how many unacknowledged messages each consumer holds. Kafka inverts this: consumers pull batches and track their own offsets.
When to use RabbitMQ and when to use Kafka?
Use RabbitMQ when the workload needs broker-side routing, per-message TTL, protocol breadth (AMQP, MQTT, STOMP), or broker-level dead lettering. Use Kafka when many consumers need to read the same durable, replayable history at volume.
Which is better for microservices, Kafka or RabbitMQ?
It depends on the interaction style. Request-reply and routed task handoff between services fit RabbitMQ’s exchange model. An event backbone that many services read independently, with replay when a consumer breaks, is what Kafka’s log model provides.