DEV Community

Tech Forge
Tech Forge

Posted on

Message Queues Explained with Practical Examples

What Is a Message Queue?

A message queue is a buffer that stores messages between producers and consumers. Producers send data to the queue, and consumers read from it. The queue decouples the two sides so they don't need to know about each other. This is a core pattern in distributed systems.

Think of it like a restaurant ordering system. You (the producer) write your order on a ticket and put it on a spindle. The kitchen (the consumer) picks tickets off the spindle when they're ready. You don't shout at the chef, and the chef doesn't wait for you. The spindle is the queue.

Why Use a Message Queue?

Three big reasons:

  1. Decoupling: Producers and consumers evolve independently. You can change one without touching the other.
  2. Buffering: Producers can run faster than consumers. The queue absorbs spikes and prevents overload.
  3. Scaling: You can add more consumers to handle more load, or more producers to generate more work.

Core Concepts

  • Producer: Sends messages.
  • Consumer: Receives messages.
  • Queue: Stores messages until consumed.
  • Broker: The server that hosts the queue (e.g., RabbitMQ, Kafka, Redis).
  • Acknowledgment: When a consumer tells the broker it successfully processed a message.
  • Dead Letter Queue: Where messages go if they can't be processed after retries.

Simple Example with Redis

Redis has a simple list-based queue using LPUSH and BRPOP. Here's a minimal Python example using redis-py.

import redis
import time

r = redis.Redis(host='localhost', port=6379)

# Producer
r.lpush('tasks', 'send_email')
r.lpush('tasks', 'generate_report')

# Consumer (blocking pop)
while True:
    task = r.brpop('tasks', timeout=5)
    if task:
        print(f"Processing: {task[1].decode()}")
        time.sleep(1)  # simulate work
    else:
        break
Enter fullscreen mode Exit fullscreen mode

This is a simple FIFO queue. It works for basic cases but lacks features like acknowledgments, retries, and routing.

Real-World Example with RabbitMQ

RabbitMQ is a full-featured broker. Here's a producer and consumer in Python using pika.

Producer (send.py):

import pika

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

channel.queue_declare(queue='hello')

channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print("Sent 'Hello World!'")
connection.close()
Enter fullscreen mode Exit fullscreen mode

Consumer (receive.py):

import pika, sys, os

def callback(ch, method, properties, body):
    print(f"Received {body}")
    ch.basic_ack(delivery_tag=method.delivery_tag)

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_consume(queue='hello', on_message_callback=callback)

print('Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
Enter fullscreen mode Exit fullscreen mode

Note the basic_ack. If the consumer crashes before acking, RabbitMQ redelivers the message to another consumer. That's a key feature for reliability.

Patterns: Work Queues vs Pub/Sub

Two common patterns:

  • Work Queue: Each message goes to exactly one consumer. Good for distributing tasks.
  • Publish/Subscribe: Each message goes to all consumers. Good for broadcasting events.

In RabbitMQ, work queues use a single queue. Pub/Sub uses exchanges and multiple queues bound to them.

When Not to Use a Message Queue

Queues add complexity. Don't use one if:

  • Your system is a small monolith with no scaling needs.
  • You need immediate synchronous responses (use HTTP or gRPC).
  • Your data is highly transactional and requires strict ordering across all operations.

Common Pitfalls

  • Forgetting to ack: Messages get redelivered endlessly.
  • Poison messages: A message that always fails. Use a dead letter queue.
  • Ordering issues: Most queues don't guarantee global order across multiple consumers. If you need strict ordering, use a single consumer or partition keys.
  • Monitoring: Without metrics, you're blind to queue depth and consumer lag.

Conclusion

Message queues are a powerful tool for building resilient, scalable systems. Start simple with Redis for basic needs, move to RabbitMQ or Kafka for production-grade features. Remember to handle acknowledgments, retries, and monitoring from day one.

The key takeaway: decouple your components and let the queue handle the handoff. Your future self will thank you when a spike in traffic doesn't crash your service.

Top comments (0)