Apache Kafka and RabbitMQ are the two most widely used message brokers, but they solve fundamentally different problems. Kafka is a distributed event streaming platform built for high-throughput, ordered, replayable event logs. RabbitMQ is a traditional message broker built for flexible routing, task queues, and reliable point-to-point delivery. Choosing the wrong one is an expensive mistake — this guide explains exactly when to use each.
At a glance
| Apache Kafka | RabbitMQ | |
|---|---|---|
| First release | 2011 (LinkedIn) | 2007 (Rabbit Technologies) |
| Model | Distributed event log (pull) | Message broker (push) |
| Throughput | 1M+ messages/sec | 20k–50k messages/sec |
| Message retention | Configurable (days/weeks/forever) | Deleted after acknowledgement |
| Message ordering | Per partition (guaranteed) | Per queue (guaranteed) |
| Routing | Topic + partition key | Exchange types (direct/fanout/topic/headers) |
| Consumer model | Pull-based, offset tracking | Push-based, acknowledgement |
| Replay | Yes — consumers can re-read messages | No — once consumed, gone |
| Protocol | Custom binary (TCP) | AMQP 0-9-1, MQTT, STOMP |
| Delivery semantics | At-least-once, exactly-once (with config) | At-most-once, at-least-once |
| Best for | Event streaming, analytics, CDC, log aggregation | Task queues, RPC, microservices messaging |
What is Apache Kafka?
Kafka is a distributed, partitioned, replicated commit log. Messages are organized into topics, each split into partitions. Producers write messages to partitions; consumers track their position with an offset and pull messages at their own pace.
Key architectural ideas:
- Messages are retained on disk (not deleted on consumption) — this enables replay
- Each partition is an ordered, immutable sequence of records
- Consumer groups allow multiple consumers to share the load across partitions
- Kafka scales horizontally: add partitions and brokers to handle more load
- Brokers are stateless — consumer state (offsets) lives in a special
__consumer_offsetstopic or an external system
Topic: orders (3 partitions)
Partition 0: [msg0] [msg1] [msg4] [msg7]
Partition 1: [msg2] [msg5] [msg8]
Partition 2: [msg3] [msg6] [msg9]
Consumer Group A (offset 5 on each partition):
Consumer 1 → Partition 0
Consumer 2 → Partition 1
Consumer 3 → Partition 2
Consumer Group B (offset 0 — re-reading from the start):
Consumer 1 → Partition 0, 1, 2 (all partitions)
What is RabbitMQ?
RabbitMQ is a message broker implementing the AMQP protocol. Producers send messages to an exchange, which routes them to one or more queues based on bindings and routing keys. Consumers subscribe to queues and receive messages pushed to them.
Key architectural ideas:
- Messages live in queues; once consumed and acknowledged, they're deleted
- Exchanges handle routing — four built-in types:
direct,fanout,topic,headers - Consumers tell the broker "I'm ready" (prefetch count); broker pushes messages
- Dead Letter Exchanges (DLX) handle failed/expired messages
- Queues can be durable (survives restart) or transient
Producer
│
▼
Exchange (type: topic)
│
├── binding: "order.created" ──► Queue: order-processing
├── binding: "order.*" ──► Queue: order-analytics
└── binding: "#" ──► Queue: audit-log
Consumer A ◄── order-processing
Consumer B ◄── order-analytics
Consumer C ◄── audit-log
Messaging models compared
| Dimension | Kafka | RabbitMQ |
|---|---|---|
| Consumer type | Pull (poll) | Push (subscription) |
| Message lifetime | Retention-based (not tied to consumption) | Consumption-based (deleted after ACK) |
| Replay | Yes — seek to any offset | No |
| Competing consumers | Via consumer groups (each group gets all messages) | Via queue (workers share messages) |
| Message ordering | Strict per partition | Strict per queue |
| Priority queues | Not supported natively | Yes (x-max-priority) |
| Request-reply (RPC) | Awkward | First-class (reply-to, correlation-id) |
| Delayed messages | Not native (needs plugin or external) | Yes (rabbitmq-delayed-message-exchange) |
Throughput and performance
Kafka is in a completely different performance league:
- Designed from the ground up for sequential disk writes (OS page cache)
- Batch compression (Snappy, LZ4, ZSTD) reduces network I/O by 4–8×
- Zero-copy transfer via
sendfile()syscall - Typical benchmark: 1–2 million messages/sec on commodity hardware
- LinkedIn processes 7 trillion messages/day on Kafka
RabbitMQ is fast for its use cases but not designed for extreme throughput:
- Optimized for low-latency delivery and complex routing logic
- Typical benchmark: 20k–50k messages/sec per node
- Performance degrades significantly with large message backlogs (queue depth)
- Can be tuned with prefetch, lazy queues, and quorum queues
| Scenario | Kafka | RabbitMQ |
|---|---|---|
| Sustained throughput | 1M+ msg/sec | 20–50k msg/sec |
| Latency (p99) | 5–15ms | 1–5ms |
| Large backlogs | Unaffected (log-based) | Degrades (memory pressure) |
| Many small messages | Very efficient (batch) | Good |
| Slow consumers | No problem (offset-based) | Queue builds up |
Message routing
Kafka routing is simple and explicit:
- Producers choose a topic and optionally a partition key
- Same key → same partition → ordering guaranteed
- Range or hash partitioning built-in
- No server-side routing logic — consumers decide what to read
# Kafka producer with explicit partition key
producer.send(
topic="orders",
key=customer_id.encode(), # same customer → same partition
value=order_json.encode()
)
RabbitMQ routing is flexible and server-side:
| Exchange type | Routing logic | Use case |
|---|---|---|
direct |
Exact routing key match | Point-to-point, error queues |
fanout |
Broadcast to all bound queues | Pub/sub, notifications |
topic |
Wildcard matching (* = one word, # = zero or more) |
Event categorization |
headers |
Match on message header attributes | Complex content-based routing |
# RabbitMQ producer with topic exchange
channel.basic_publish(
exchange="events",
routing_key="order.created.eu", # matches "order.*" and "order.#"
body=json.dumps(order)
)
Delivery semantics
| Guarantee | Kafka | RabbitMQ |
|---|---|---|
| At-most-once | Disable retries | autoAck=true |
| At-least-once | Default (retry on failure) | Manual ACK + durable queue |
| Exactly-once | Yes — idempotent producer + transactions | Not native (requires application logic) |
Kafka exactly-once (end-to-end):
# Producer: enable idempotence
producer = KafkaProducer(
bootstrap_servers="localhost:9092",
enable_idempotence=True, # dedup within session
acks="all", # wait for all replicas
transactional_id="order-svc-1" # for cross-partition transactions
)
RabbitMQ at-least-once:
# Consumer: manual acknowledgement
def callback(ch, method, properties, body):
try:
process(body)
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
channel.basic_consume(queue="tasks", on_message_callback=callback)
Code examples
Kafka: Producer and Consumer (Python)
# pip install kafka-python
from kafka import KafkaProducer, KafkaConsumer
import json
# Producer
producer = KafkaProducer(
bootstrap_servers=["localhost:9092"],
value_serializer=lambda v: json.dumps(v).encode()
)
producer.send("orders", key=b"customer-42", value={"order_id": 1, "amount": 99.99})
producer.flush()
# Consumer (consumer group)
consumer = KafkaConsumer(
"orders",
bootstrap_servers=["localhost:9092"],
group_id="order-processor",
auto_offset_reset="earliest",
value_deserializer=lambda m: json.loads(m.decode())
)
for msg in consumer:
print(f"Partition {msg.partition}, Offset {msg.offset}: {msg.value}")
# Offset auto-committed (or manual with enable_auto_commit=False)
RabbitMQ: Producer and Consumer (Python)
# pip install pika
import pika
import json
connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
channel = connection.channel()
# Declare a durable queue
channel.queue_declare(queue="tasks", durable=True)
# Producer
def publish_task(task):
channel.basic_publish(
exchange="",
routing_key="tasks",
body=json.dumps(task),
properties=pika.BasicProperties(
delivery_mode=pika.DeliveryMode.Persistent # survive broker restart
)
)
publish_task({"task_id": 1, "type": "email", "recipient": "user@example.com"})
# Consumer
channel.basic_qos(prefetch_count=1) # process one at a time
def callback(ch, method, properties, body):
task = json.loads(body)
print(f"Processing task {task['task_id']}")
# ... do work ...
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue="tasks", on_message_callback=callback)
channel.start_consuming()
Message retention and replay
This is one of the biggest architectural differences.
Kafka stores messages regardless of consumption:
- Default retention: 7 days (configurable per topic)
log.retention.bytesto limit by sizelog.retention.ms=-1for infinite retention (event sourcing)- Consumers can seek to any offset: replay from start, from timestamp, from end
# Replay from the beginning
kafka-consumer-groups.sh --reset-offsets \
--group my-consumer \
--topic orders \
--to-earliest \
--execute
RabbitMQ deletes messages after acknowledgement:
- Once a consumer ACKs, the message is gone
- Unacked messages return to queue on consumer disconnect
- No native replay — you need to archive messages externally
- Dead Letter Queues (DLQ) capture failed messages
Scalability
Kafka scaling:
- Add partitions to increase parallelism (consumers ≤ partitions per group)
- Add brokers to distribute load
- Replication factor controls fault tolerance
- Practically unlimited horizontal scale
RabbitMQ scaling:
- Quorum queues (recommended over mirrored queues) provide HA
- Cluster of nodes for redundancy
- Shovel/Federation plugins for WAN scenarios
- Horizontal scaling is less straightforward than Kafka
Setup and operations
| Aspect | Kafka | RabbitMQ |
|---|---|---|
| Setup complexity | Higher — needs ZooKeeper (or KRaft mode in 2.8+) | Simpler — single binary |
| Managed cloud | MSK (AWS), Confluent Cloud, Aiven | Amazon MQ, CloudAMQP, Aiven |
| Monitoring | Kafka JMX metrics, Prometheus exporter | Management UI (port 15672) built-in |
| Schema management | Schema Registry (Confluent) | No built-in |
| Multi-tenancy | Multiple clusters or namespaced topics | Virtual hosts |
| Docker quick-start | 2 containers (ZK + Kafka) or 1 (KRaft) | 1 container |
# Kafka with KRaft (no ZooKeeper, Kafka 3.3+)
docker run -d \
-e KAFKA_NODE_ID=1 \
-e KAFKA_PROCESS_ROLES=broker,controller \
-e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
-p 9092:9092 \
apache/kafka:3.7.0
# RabbitMQ with management UI
docker run -d \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:3-management
# UI: http://localhost:15672 (guest/guest)
Where Kafka wins
| Scenario | Why Kafka |
|---|---|
| Event streaming | Built for ordered, high-throughput event logs |
| Log aggregation | Central log sink from many services |
| Change Data Capture (CDC) | Database events → Debezium → Kafka |
| Stream processing | Native integration with Kafka Streams, Flink |
| Event sourcing | Immutable event log with infinite retention |
| Analytics pipelines | Feed Spark, Flink, ClickHouse in real-time |
| Audit trail | Replayable history of all events |
| Multiple consumers | Each consumer group gets all messages independently |
Where RabbitMQ wins
| Scenario | Why RabbitMQ |
|---|---|
| Task queues | Workers compete for tasks; ACK removes from queue |
| RPC patterns | Built-in reply-to + correlation-id support |
| Complex routing | Topic/headers exchanges route flexibly |
| Priority queues | x-max-priority header |
| Delayed/scheduled tasks | Delayed message exchange plugin |
| Low-latency delivery | Push model; sub-millisecond in practice |
| Per-message TTL | Expire individual messages |
| Simple use cases | No need for partition planning or offsets |
Full comparison table
| Feature | Kafka | RabbitMQ |
|---|---|---|
| Model | Distributed log | Message broker (AMQP) |
| Protocol | Custom binary | AMQP, MQTT, STOMP |
| Throughput | 1M+ msg/sec | 20–50k msg/sec |
| Latency (p99) | 5–15ms | 1–5ms |
| Message retention | Log-based (configurable) | Until ACK |
| Replay | ✅ Yes | ❌ No |
| Message ordering | Per partition | Per queue |
| Consumer model | Pull | Push |
| Routing | By partition key | Exchange bindings |
| Priority messages | ❌ No | ✅ Yes |
| Delayed messages | ❌ No (without plugin) | ✅ Yes (plugin) |
| Dead letter queue | Via separate topic | ✅ Native DLX |
| Exactly-once | ✅ Yes (with config) | ❌ Not native |
| Schema registry | Confluent Schema Registry | ❌ Not built-in |
| Stream processing | ✅ Kafka Streams, Flink | ❌ Not designed for it |
| Horizontal scale | ✅ Excellent | ⚠️ Limited |
| Setup complexity | Higher | Lower |
| Management UI | Kafka UI (external) | ✅ Built-in |
| Cloud managed | MSK, Confluent | Amazon MQ, CloudAMQP |
| License | Apache 2.0 | Mozilla Public License 2.0 |
Decision guide
Do you need to replay messages or audit history?
├── YES → Kafka
└── NO
│
├── Do you need >100k msg/sec throughput?
│ ├── YES → Kafka
│ └── NO
│ │
│ ├── Do you need complex routing (topic/header matching)?
│ │ ├── YES → RabbitMQ
│ │ └── NO
│ │ │
│ │ ├── Do you need task queues with competing workers?
│ │ │ ├── YES → RabbitMQ
│ │ │ └── NO
│ │ │ │
│ │ │ └── Are you building event streaming / CDC / analytics?
│ │ │ ├── YES → Kafka
│ │ │ └── NO → Either works; RabbitMQ simpler to start
Kafka vs RabbitMQ vs alternatives
| Kafka | RabbitMQ | Redis Streams | NATS | Apache Pulsar | |
|---|---|---|---|---|---|
| Model | Event log | AMQP broker | Log (in Redis) | PubSub | Unified log+queue |
| Throughput | 1M+/sec | 50k/sec | 500k/sec | 10M+/sec (tiny msgs) | 1M+/sec |
| Persistence | Disk (days/∞) | Until ACK | TTL or stream | Optional | Tiered storage |
| Replay | ✅ | ❌ | ✅ | ❌ | ✅ |
| Complexity | High | Low | Very low | Very low | High |
| Best for | Streaming, CDC | Task queues, RPC | Simple pub/sub | IoT, microservices | Unified Kafka+RabbitMQ |
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Too few Kafka partitions | Can't scale consumers beyond partition count | Plan for 2–4× expected consumers; prefer more partitions |
| Using Kafka for simple task queues | Overkill; complex offset management | Use RabbitMQ for competing worker patterns |
| Ignoring consumer lag | Consumers fall behind, messages pile up | Monitor kafka.consumer.fetch-manager-metrics |
| No Dead Letter Queue in RabbitMQ | Failed messages silently lost | Always bind a DLX to production queues |
| Storing too much state in RabbitMQ queues | Memory pressure, performance collapse | Use lazy queues; Kafka handles large backlogs naturally |
Not setting acks=all in Kafka |
Data loss on broker failure | Always acks=all + min.insync.replicas=2 in production |
| Sharing one Kafka topic for different event types | Schema conflicts; complex filtering | One topic per event type |
| Using RabbitMQ for event sourcing | No replay, limited retention | Kafka is purpose-built for this |
FAQ
Can I use Kafka and RabbitMQ together?
Yes — many teams use both. Kafka handles high-throughput event streaming and analytics; RabbitMQ handles transactional task queues and service-to-service messaging. They serve different layers of the architecture.
Is Kafka harder to operate than RabbitMQ?
Historically yes — ZooKeeper added significant operational complexity. With KRaft mode (stable since Kafka 3.3), ZooKeeper is no longer needed and Kafka is much simpler. Use a managed service (MSK, Confluent Cloud) to minimize ops burden.
Which is better for microservices?
Both are used in microservices. RabbitMQ is popular for synchronous-style async messaging (request → queue → worker → reply). Kafka is better when microservices need to react to a shared event log (event-driven architecture).
Can RabbitMQ replay messages?
Not natively. Once a message is acknowledged it's gone. You can implement replay by storing messages in a database before publishing, or by not ACKing and re-queuing — but this is an anti-pattern. If you need replay, use Kafka.
What's the Kafka equivalent of a RabbitMQ dead letter queue?
A separate "dead letter topic." You catch processing failures in your consumer code and manually produce the failed message to a <topic>.dlq topic. There's no automatic DLX mechanism like RabbitMQ.
Does Kafka guarantee exactly-once delivery?
Yes — with enable.idempotence=true + transactional_id on the producer, and transactional consumers. This is end-to-end exactly-once within the Kafka cluster. External systems (databases) require idempotent writes on the consumer side.