Skip to content
LowLevelDesign Mastery

RabbitMQ & Traditional Queues

Traditional message broker with flexible routing

RabbitMQ is a traditional message broker that implements the AMQP (Advanced Message Queuing Protocol) standard. Unlike Kafka’s log-based approach, RabbitMQ uses a more traditional broker model where messages are delivered to consumers and then removed from queues (unless configured otherwise).

RabbitMQ’s architecture revolves around four key components:

  1. Exchanges - Receive messages from producers and route them to queues based on routing rules
  2. Queues - Store messages until they are consumed
  3. Bindings - Define the relationship between exchanges and queues (routing rules)
  4. Routing Keys - Message attributes used by exchanges to determine routing

This design provides flexible routing capabilities that Kafka doesn’t offer natively. RabbitMQ excels at scenarios requiring complex routing logic, task distribution, and point-to-point messaging.

RabbitMQ architecture where a producer publishes with a routing key to an exchange that routes via bindings to queues and consumers
  1. Exchange - Receives messages from producers and routes them to queues based on routing rules. Think of it as a post office that sorts mail.
  2. Queue - Stores messages until they are consumed. Messages are removed after successful consumption (unless configured otherwise).
  3. Binding - Defines the connection between an exchange and a queue, including the routing key pattern that determines which messages go to which queue.
  4. Routing Key - A message attribute that the exchange uses to determine which queue(s) should receive the message. It’s like an address on an envelope.

The Flow: Producer → Exchange (with routing key) → Binding (matches routing key) → Queue → Consumer

This design allows for flexible routing - you can route the same message to multiple queues, filter messages based on patterns, or broadcast to all queues, depending on the exchange type.


Routes to queue with matching routing key.

RabbitMQ direct exchange routing a message with key order.created to the queue bound with exactly the same key

Use case: Point-to-point messaging, task queues

Routes based on pattern matching (wildcards).

RabbitMQ topic exchange routing key order.created.v2 to queues bound with wildcard patterns order.* and order.created.*

Patterns:

  • * - Matches one word
  • # - Matches zero or more words

Use case: Categorized messages, event routing

Broadcasts to all bound queues (ignores routing key).

RabbitMQ fanout exchange broadcasting a copy of every published message to all three bound queues, ignoring routing keys

Use case: Pub-sub, notifications, cache invalidation

Routes based on message headers (ignores routing key).

Use case: Complex routing logic


💡 Tip: Click dropdown to switch between languages
"rabbitmq_producer.py
import pika
import json
class RabbitMQProducer:
"""RabbitMQ producer"""
def __init__(self, host='localhost'):
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host=host)
)
self.channel = self.connection.channel()
def setup_exchange(self, exchange_name: str, exchange_type: str = 'direct'):
"""Declare exchange"""
self.channel.exchange_declare(
exchange=exchange_name,
exchange_type=exchange_type,
durable=True # Survive broker restart
)
def publish(self, exchange: str, routing_key: str, message: dict):
"""Publish message"""
self.channel.basic_publish(
exchange=exchange,
routing_key=routing_key,
body=json.dumps(message),
properties=pika.BasicProperties(
delivery_mode=2, # Make message persistent
content_type='application/json'
)
)
print(f"Published to {exchange} with key {routing_key}")
def close(self):
"""Close connection"""
self.connection.close()
# Usage
producer = RabbitMQProducer()
producer.setup_exchange('orders', 'direct')
producer.publish('orders', 'order.created', {
'order_id': 123,
'user_id': 456,
'amount': 99.99
})
producer.close()
💡 Tip: Click dropdown to switch between languages
"rabbitmq_consumer.py
import pika
import json
class RabbitMQConsumer:
"""RabbitMQ consumer"""
def __init__(self, host='localhost'):
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host=host)
)
self.channel = self.connection.channel()
def setup_queue(self, queue_name: str, durable: bool = True):
"""Declare queue"""
self.channel.queue_declare(
queue=queue_name,
durable=durable # Survive broker restart
)
def bind_queue(self, queue: str, exchange: str, routing_key: str):
"""Bind queue to exchange"""
self.channel.queue_bind(
queue=queue,
exchange=exchange,
routing_key=routing_key
)
def consume(self, queue: str, handler, auto_ack: bool = False):
"""Consume messages"""
def callback(ch, method, properties, body):
try:
message = json.loads(body)
# Process message
handler(message)
# Acknowledge message
if not auto_ack:
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception as e:
print(f"Error processing message: {e}")
# Reject and requeue
if not auto_ack:
ch.basic_nack(
delivery_tag=method.delivery_tag,
requeue=True
)
# Set prefetch (how many unacked messages per consumer)
self.channel.basic_qos(prefetch_count=1)
# Start consuming
self.channel.basic_consume(
queue=queue,
on_message_callback=callback,
auto_ack=auto_ack
)
print(f"Consuming from {queue}...")
self.channel.start_consuming()
def close(self):
"""Close connection"""
self.connection.close()
# Usage
def handle_order(message):
print(f"Processing order: {message['order_id']}")
# Process order...
consumer = RabbitMQConsumer()
consumer.setup_queue('order-processor', durable=True)
consumer.bind_queue('order-processor', 'orders', 'order.created')
consumer.consume('order-processor', handle_order, auto_ack=False)

Critical for reliable message processing.

# Message removed immediately when delivered
channel.basic_consume(queue='orders', on_message_callback=callback, auto_ack=True)

Problem: If consumer crashes, message lost!

# Message removed only after ack
def callback(ch, method, properties, body):
process_message(body)
ch.basic_ack(delivery_tag=method.delivery_tag) # Acknowledge
channel.basic_consume(queue='orders', on_message_callback=callback, auto_ack=False)

Benefits:

  • Message redelivered if consumer crashes
  • Can reject and requeue on error
  • Reliable processing

FeatureRabbitMQKafka
ModelTraditional brokerStreaming platform
Message RetentionRemoved after consumptionRetained (configurable)
RoutingFlexible (exchanges)Simple (topics/partitions)
OrderingPer queuePer partition
ThroughputGoodExcellent
Use CaseTask queues, RPCEvent streaming, logs

Choose RabbitMQ when:

  • Need flexible routing
  • Task queues
  • Message removed after processing
  • Complex routing logic

Choose Kafka when:

  • Event streaming
  • High throughput
  • Message retention needed
  • Replay capability needed

Exchanges Route

Exchanges route messages to queues based on routing keys. Direct, topic, fanout, headers.

Manual Ack

Use manual acknowledgment for reliability. Auto-ack removes messages immediately (risky).

Durability

Make queues/exchanges/messages durable to survive broker restart. Critical for production.

Flexible Routing

RabbitMQ’s flexible routing (exchanges) makes it great for complex routing scenarios.