AMQP and Kafka aren’t just different protocols; they represent fundamentally distinct philosophies for handling streams of data.

Let’s see AMQP in action with a simple producer and consumer using rabbitmq-python.

# producer.py
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='hello')

message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',
                      routing_key='hello',
                      body=message)
print(f" [x] Sent '{message}'")
connection.close()
# consumer.py
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='hello')

def callback(ch, method, properties, body):
    print(f" [x] Received {body}")

channel.basic_consume(queue='hello',
                      on_message_callback=callback,
                      auto_ack=True)

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

Running python producer.py "My first message" followed by python consumer.py will show the consumer receiving "My first message". This interaction highlights AMQP’s focus on message delivery guarantees and routing flexibility.

Now, let’s look at Kafka with a producer and consumer, also demonstrating real-time data flow.

// Producer example (using Java API)
import java.util.Properties;
import org.apache.kafka.clients.producer.*;

public class SimpleProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("my-topic", Integer.toString(i), "Message " + i));
        }
        producer.close();
    }
}
// Consumer example (using Java API)
import java.util.Arrays;
import java.util.Properties;
import org.apache.kafka.clients.consumer.*;

public class SimpleConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "consumer-group-1");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));

        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (var record : records) {
                System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());
            }
        }
    }
}

Running the producer (which sends 10 messages to the "my-topic" topic) and then the consumer will show the consumer printing each message with its offset. This illustrates Kafka’s log-centric, append-only stream processing.

AMQP, commonly implemented by RabbitMQ, excels in scenarios demanding complex routing and guaranteed message delivery. It’s built around the concept of exchanges and queues. Producers send messages to exchanges, which then route them to one or more queues based on defined bindings and routing keys. Consumers then subscribe to these queues. This model provides fine-grained control over message flow, enabling patterns like fanout (sending to all queues), direct (sending to a specific queue), topic (sending based on pattern matching), and headers (sending based on message headers). AMQP brokers typically store messages in memory or on disk until they are successfully consumed and acknowledged, ensuring no message is lost.

Kafka, on the other hand, is a distributed streaming platform designed for high-throughput, fault-tolerant, and scalable data pipelines. It treats data as a continuous stream of records organized into topics. Each topic is partitioned, and these partitions are ordered, immutable logs. Producers append records to the end of a partition, and consumers read from these partitions at their own pace, maintaining their own offset. This log-based architecture allows for multiple consumers to read the same data independently, replay historical data, and achieve very high read/write performance. Kafka’s primary strength lies in its ability to handle massive volumes of data in real-time, making it ideal for event sourcing, log aggregation, and stream processing applications.

The core difference lies in their fundamental abstraction: AMQP is a messaging protocol focused on reliable delivery and flexible routing of individual messages, while Kafka is a distributed commit log designed for high-volume, fault-tolerant streaming of ordered records. AMQP brokers act like sophisticated post offices, ensuring each letter (message) gets to the right recipient (queue) with proof of delivery. Kafka acts more like a vast, append-only newspaper archive; once an edition (batch of records) is published, anyone can subscribe to read it from any point, and new editions are continuously added.

When deciding, consider your primary use case. If you need guaranteed delivery of individual messages with complex routing logic, or if you’re integrating diverse applications with varying messaging needs, AMQP (like RabbitMQ) is often a better fit. Its rich feature set for message lifecycle management and its mature routing capabilities are invaluable.

If you’re dealing with massive data streams, require high throughput and low latency for real-time analytics, or need the ability to replay events, Kafka is the clear winner. Its distributed nature, log-based storage, and consumer-driven offset management are built for these high-scale, high-volume streaming scenarios.

A key aspect of Kafka’s design that often surprises people is its consumer group mechanism. When multiple consumers are part of the same group and subscribe to the same topic, Kafka ensures that each partition is consumed by only one consumer within that group. This is how Kafka achieves scalable consumption: you can add more consumers to a group to parallelize processing across partitions, but you can’t have two consumers in the same group processing the same partition’s data concurrently.

The choice between AMQP and Kafka often boils down to whether you need a message queue with advanced routing and delivery guarantees or a distributed streaming platform for high-throughput data pipelines.

The next logical comparison is often between Kafka and Pulsar, which shares some similarities with Kafka but introduces further architectural innovations.

Want structured learning?

Take the full Amqp course →