10  Message Queues & Communication Protocols

10.1 Message Queues

10.1.1 What is a Message Queue?

A message queue is a form of asynchronous service-to-service communication used in distributed systems. Messages are stored in a queue until they are processed and deleted.

Message Queue Architecture

10.1.2 Key Concepts

Components:

  • Producer: Sends messages to the queue
  • Queue: Stores messages temporarily
  • Consumer: Receives and processes messages
  • Message: Data being transmitted

Basic Flow:

Producer → Message Queue → Consumer

10.1.3 Why Use Message Queues?

Benefits:

  • Asynchronous Processing: Producer doesn’t wait for consumer
  • Decoupling: Producer and consumer independent
  • Load Leveling: Queue absorbs traffic spikes
  • Reliability: Messages not lost if consumer unavailable
  • Scalability: Add more consumers to process faster
  • Fault Tolerance: Retry failed messages

Example Use Case:

E-commerce Order Flow:
Order Service → Queue → [Inventory Service, Email Service, Analytics Service]

Without Queue:
- Order Service waits for all services
- If Email Service down, order fails

With Queue:
- Order Service sends to queue and returns
- Services process asynchronously
- If Email Service down, message retried later

10.1.4 Message Queue Patterns

1. Point-to-Point (Queue)

Characteristics:

  • One message → One consumer
  • Message deleted after processing
  • Load balanced across consumers
Producer → Queue → [Consumer 1 OR Consumer 2 OR Consumer 3]

Use cases:

  • Task distribution
  • Job processing
  • Command execution

2. Publish-Subscribe (Topic)

Characteristics:

  • One message → Multiple consumers
  • Each subscriber gets copy
  • Broadcast pattern
Publisher → Topic → [Subscriber 1 AND Subscriber 2 AND Subscriber 3]

Use cases:

  • Event notifications
  • Real-time updates
  • Fan-out scenarios

Example:

User Registration Event:
User Service publishes "UserRegistered" →
    Email Service (sends welcome email)
    Analytics Service (tracks signup)
    CRM Service (creates lead)

3. Request-Reply

Characteristics:

  • Producer expects response
  • Correlation ID to match requests/responses
Service A → Request Queue → Service B
Service A ← Reply Queue ← Service B

10.1.5 Message Properties

Message Components:

  • Headers: Metadata (timestamp, ID, priority)
  • Body: Actual data (JSON, XML, binary)
  • Properties: Custom attributes

Example Message:

{
  "messageId": "msg-123",
  "timestamp": "2024-01-01T10:00:00Z",
  "priority": 5,
  "body": {
    "orderId": "order-456",
    "userId": "user-789",
    "total": 99.99
  },
  "headers": {
    "retry-count": 0,
    "source": "order-service"
  }
}

10.1.7 Message Delivery Guarantees

1. At-Most-Once

Behavior: Message delivered 0 or 1 times (may be lost)

How: No acknowledgment required

Use cases:

  • Metrics collection
  • Non-critical logs
  • When performance > reliability

2. At-Least-Once

Behavior: Message delivered 1 or more times (may duplicate)

How: Acknowledgment required, retry on failure

Use cases:

  • Most common
  • Idempotent operations
  • Order processing (with deduplication)

Consumer must be idempotent:

// Store processed message IDs to avoid duplicates
async function processMessage(message) {
    if (await isProcessed(message.id)) {
        return; // Already processed, skip
    }

    await doWork(message);
    await markProcessed(message.id);
}

3. Exactly-Once

Behavior: Message delivered exactly 1 time

How: Complex coordination (transactions, deduplication)

Use cases:

  • Financial transactions
  • Critical operations
  • When duplicates unacceptable

Challenges:

  • Performance overhead
  • Requires transactional support
  • Hard to achieve in distributed systems

10.1.8 Best Practices

1. Idempotent Consumers

Ensure processing same message multiple times has same effect as once.

// Bad (not idempotent)
function processOrder(order) {
    inventory -= order.quantity; // Subtracts again on retry!
}

// Good (idempotent)
function processOrder(order) {
    if (!isProcessed(order.id)) {
        inventory -= order.quantity;
        markProcessed(order.id);
    }
}

2. Dead Letter Queues (DLQ)

Handle messages that fail repeatedly.

Queue → (Process) → Success
   ↓
 (Fail after 3 retries)
   ↓
Dead Letter Queue → Manual Review

3. Message Expiration (TTL)

Set time-to-live to avoid processing stale messages.

producer.send({
    body: message,
    expiration: 3600000 // 1 hour
});

4. Monitoring

Track:

  • Queue depth (backlog)
  • Processing rate
  • Error rate
  • Message age

Alert when:

  • Queue depth growing
  • High error rate
  • Old messages in queue

5. Partitioning for Scalability

Topic: orders
Partition 0: orders for users 0-999
Partition 1: orders for users 1000-1999
Partition 2: orders for users 2000-2999

Consumers can process partitions in parallel

10.2 Communication Models

10.2.1 1. Push Model

Characteristics:

  • Sender actively sends messages when available
  • Receiver waits for messages
  • Lower latency

Example:

Server → (push) → Client WebSocket
Email Server → (push) → Mobile Push Notification

Pros:

  • Real-time updates
  • Lower latency
  • Server controls flow

Cons:

  • Receiver must be ready
  • Can overwhelm receiver
  • Connection must be maintained

10.2.2 2. Pull Model

Characteristics:

  • Receiver asks sender if messages available
  • Polling mechanism
  • Receiver controls rate

Example:

Client → (poll every 5s) → Server: "Any new messages?"
Consumer → (pull) → Message Queue

Pros:

  • Receiver controls rate
  • Works with intermittent connectivity
  • Simple to implement

Cons:

  • Higher latency
  • Unnecessary polls (waste resources)
  • Not real-time

Optimization: Long Polling

Client → Server: "Any messages?"
Server waits (hold connection) until message arrives or timeout
Server → Client: Message (or timeout)
Client immediately sends next request

10.3 Communication Protocols

10.3.1 HTTP (Hypertext Transfer Protocol)

Characteristics:

  • Request-response protocol
  • Stateless
  • Text-based

HTTP Protocol

Methods:

  • GET: Retrieve data
  • POST: Create data
  • PUT: Update data
  • DELETE: Delete data

Pros:

  • ✅ Universal support
  • ✅ Well understood
  • ✅ Firewall-friendly
  • ✅ Caching support

Cons:

  • ❌ Not real-time
  • ❌ Overhead (headers)
  • ❌ Half-duplex (one direction at a time)

Use cases:

  • RESTful APIs
  • Traditional web apps
  • Simple request-response

10.3.2 Long Polling

How it works:

  1. Client sends request
  2. Server holds connection open
  3. Server responds when data available or timeout
  4. Client immediately reconnects

Long Polling

Example:

async function longPoll() {
    while (true) {
        const response = await fetch('/api/messages?timeout=30');
        const messages = await response.json();

        if (messages.length > 0) {
            processMessages(messages);
        }

        // Immediately reconnect
    }
}

Pros:

  • More real-time than regular polling
  • Works over HTTP
  • No special infrastructure

Cons:

  • Still some latency
  • Server resources (open connections)
  • Not truly bidirectional

10.3.3 WebSockets

Characteristics:

  • Full-duplex communication
  • Persistent connection
  • Low latency
  • Binary or text data

WebSockets

How it works:

  1. HTTP handshake (upgrade request)
  2. Connection upgraded to WebSocket
  3. Bidirectional messages
  4. Connection stays open

Example:

// Client
const ws = new WebSocket('wss://example.com/socket');

ws.onopen = () => {
    ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }));
};

ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    displayMessage(message);
};

// Server (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    ws.on('message', (data) => {
        // Broadcast to all clients
        wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(data);
            }
        });
    });
});

Pros:

  • ✅ True real-time, bidirectional
  • ✅ Low latency
  • ✅ Less overhead than HTTP polling
  • ✅ Server can push anytime

Cons:

  • ❌ More complex than HTTP
  • ❌ Firewall/proxy issues
  • ❌ Connection management needed
  • ❌ No automatic reconnection

Use cases:

  • Chat applications
  • Real-time gaming
  • Live dashboards
  • Collaborative editing
  • Financial tickers

10.3.4 gRPC

Characteristics:

  • RPC (Remote Procedure Call) framework
  • Protocol Buffers (binary serialization)
  • HTTP/2 based
  • Supports streaming

Example:

// Protocol Buffer definition
service UserService {
    rpc GetUser(UserRequest) returns (UserResponse);
    rpc StreamUsers(Empty) returns (stream UserResponse);
}

message UserRequest {
    int32 id = 1;
}

message UserResponse {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

Pros:

  • Very fast (binary)
  • Strongly typed
  • Code generation
  • Bidirectional streaming
  • Built-in auth, load balancing

Cons:

  • Not human-readable
  • Browser support limited
  • Learning curve

Use cases:

  • Microservice communication
  • Mobile clients
  • High-performance APIs

10.3.5 GraphQL

Characteristics:

  • Query language for APIs
  • Client specifies exact data needed
  • Single endpoint

Example:

# Query
query {
    user(id: 123) {
        name
        email
        posts {
            title
            createdAt
        }
    }
}

# Response
{
    "data": {
        "user": {
            "name": "John Doe",
            "email": "john@example.com",
            "posts": [
                { "title": "Hello", "createdAt": "2024-01-01" }
            ]
        }
    }
}

Pros:

  • No over-fetching
  • Single request for multiple resources
  • Strong typing (schema)
  • Versioning not needed

Cons:

  • Complexity
  • Caching harder
  • Learning curve
  • Can be inefficient (N+1 queries)

10.4 Choosing Communication Protocol

Protocol Latency Complexity Bidirectional Use Case
HTTP High Low No REST APIs, web
Long Polling Medium Low Pseudo Simple real-time
WebSockets Very Low Medium Yes Chat, gaming, live updates
gRPC Low High Yes Microservices
GraphQL Medium High No Complex data requirements

10.5 Summary

Message Queues:

  • Enable asynchronous communication
  • Decouple producers and consumers
  • Provide reliability and scalability
  • Choose based on throughput, ordering, delivery guarantees

Communication Models:

  • Push: Real-time, sender-driven
  • Pull: Receiver-driven, simpler

Protocols:

  • HTTP: Universal, simple, request-response
  • Long Polling: Better than polling, still HTTP-based
  • WebSockets: Real-time, bidirectional, persistent
  • gRPC: High-performance microservices
  • GraphQL: Flexible data fetching

Choose based on:

  • Real-time requirements
  • Complexity tolerance
  • Infrastructure capabilities
  • Performance needs