Toolmingo
Guides12 min read

Kafka vs RabbitMQ: Which Message Broker to Choose in 2025?

An in-depth comparison of Apache Kafka and RabbitMQ — architecture, throughput, message retention, routing, delivery semantics, and when to use each.

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_offsets topic 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.bytes to limit by size
  • log.retention.ms=-1 for 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.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools