AMQP’s architecture centers on the broker, which receives messages from producers, routes them through exchanges (direct, fanout, topic, or headers) to bound queues, and delivers them to consumers. Unlike MQTT’s simple topic matching, AMQP exchanges use binding rules and routing keys for sophisticated server-side message routing, with channel multiplexing allowing multiple logical channels over a single TCP connection.
65.1 Learning Objectives
By the end of this chapter, you will be able to:
Describe AMQP Architecture: Explain the roles of producers, brokers, exchanges, queues, and consumers in the AMQP messaging model
Configure Exchange Types: Set up direct, fanout, topic, and headers exchanges for different message routing patterns
Implement Message Routing: Design binding rules that route messages from exchanges to queues based on routing keys
Demonstrate Channel Multiplexing: Explain how multiple logical channels share a single TCP connection and calculate the resource savings of multiplexing over separate connections
Key Concepts
AMQP Broker: Central server receiving, routing, and storing messages between producers and consumers
Exchange: AMQP routing component that applies rules to determine which queues receive each message
Queue: Message buffer storing messages until consumed; configurable as durable (survives restart) or transient
Binding: Rule connecting an exchange to a queue with an optional routing key pattern
Channel: Lightweight virtual connection multiplexed over a single TCP connection — reduces connection overhead
Virtual Host (vhost): Isolated AMQP namespace providing multi-tenancy on a single broker instance
Routing Key: Message attribute used by direct and topic exchanges to match binding patterns for queue selection
65.2 Prerequisites
Before diving into this chapter, you should be familiar with:
AMQP Fundamentals: This chapter builds directly on AMQP basics - you must understand the protocol’s purpose, history (AMQP 0-9-1 vs 1.0), and core message-oriented middleware concepts
Layered Network Models: AMQP operates at the application layer (Layer 7), so understanding how it sits atop TCP/IP helps grasp its role in the protocol stack
Networking Basics: Knowledge of TCP connections, ports, and client-server communication patterns provides context for understanding AMQP’s connection model
Imagine an enterprise system where hundreds of applications need to communicate: banking transactions, inventory updates, customer notifications, audit logs. Direct application-to-application connections would be a nightmare - each app would need code to talk to dozens of others. AMQP (Advanced Message Queuing Protocol) solves this with a message broker - a central post office that routes messages.
The Key Components:
Producers (publishers) send messages to the broker
Exchanges receive messages and route them based on rules
Queues store messages until consumers are ready
Consumers (subscribers) receive and process messages
What makes AMQP powerful? Flexible routing. An exchange can route one message to many queues (fanout), route based on exact routing keys (direct), or use pattern matching (topic). For example, a topic exchange with routing key “sensor.temperature.warehouse” can deliver to queues subscribing to “sensor.“,”.temperature.*“, or”sensor.temperature.warehouse”.
Term
Simple Explanation
Message Broker
Central server routing messages between applications
Producer
Application that sends messages (publisher)
Consumer
Application that receives messages (subscriber)
Exchange
Routing component - determines which queues receive messages
Queue
Buffer storing messages until consumer reads them
Binding
Rule connecting exchange to queue with routing criteria
Sensor Squad: The Message Sorting Office
“I have a temperature reading to report!” announced Sammy the Sensor, holding up a tiny data packet. “But there are so many different systems that need my data – the dashboard, the alarm system, the energy manager. How do I send it to all of them?”
Max the Microcontroller grinned. “You don’t have to figure that out yourself, Sammy. That’s what the AMQP broker does! Think of it like a really smart post office. You just drop your message at the front desk – that’s the exchange – and label it with a topic like ‘temperature.kitchen.high’. The exchange checks its routing rules and puts copies into different queues – one for the alarm team, one for the dashboard team, one for the energy team.”
“So I only send one message, but it reaches everyone who needs it?” Sammy asked. “Exactly!” said Lila the LED, blinking excitedly. “And the best part is, if the alarm system is busy, the message waits safely in its queue until the alarm is ready to read it. Nobody loses any data!”
Bella the Battery nodded approvingly. “And since Sammy only talks to one exchange instead of three different systems, he uses way less power. One delivery instead of three – my kind of efficiency!”
Common Misconception: “Publishing to Queues Directly”
Misconception: “I should publish messages directly to queues for better performance, bypassing exchanges.”
Reality: Publishing directly to queues bypasses AMQP’s routing intelligence and creates tight coupling. 94% of AMQP performance issues stem from architectural anti-patterns, not protocol overhead.
Why exchanges are essential:
Anti-pattern (direct queue publishing):
# Producer tightly coupled to queue namechannel.basic_publish( exchange='', # Default exchange routing_key='temperature_queue', # Direct queue name body='22.5C')# Problem: Producer must know queue name, can't route to multiple consumers
Key principle: AMQP exchanges enable location transparency - producers don’t know (or care) who consumes messages. This is fundamental to scalable, evolvable architectures.
65.3 How It Works: AMQP Message Routing
Understanding AMQP’s message routing flow is essential for designing reliable messaging systems. The process involves three coordinated steps:
Step 1: Producer Publishes to Exchange
When a producer sends a message, it targets an exchange (not a queue directly). The message includes: - Routing key: A label like sensor.temperature.zone1 that the exchange uses for routing decisions - Message body: The actual data payload (JSON, binary, etc.) - Properties: Metadata like delivery mode, priority, content type
Step 2: Exchange Evaluates Bindings
The exchange examines its bindings (routing rules) to determine which queues should receive the message: - Direct exchange: Compares routing key to binding keys for exact matches - Topic exchange: Matches routing key against wildcard patterns (* for one word, # for zero or more) - Fanout exchange: Ignores routing key and copies message to all bound queues - Headers exchange: Matches message header attributes instead of routing key
Step 3: Queue Storage and Consumer Delivery
Once routed, the message: - Persists in queue (memory or disk based on durability settings) - Waits for consumers to request delivery (pull model) or broker pushes to subscribed consumers - Acknowledges after consumer confirms successful processing (manual ack) or immediately (auto ack)
Real-World Timeline:
T=0ms: Producer publishes to exchange
T=1ms: Exchange evaluates 100 bindings (pattern matching)
T=2ms: Message copied to 3 matching queues
T=3ms: Queue #1 delivers to consumer A
T=15ms: Consumer A finishes processing, sends ACK
T=16ms: Queue #1 removes message
CPU load: $ = = , = \({cpuTimePerSec.toFixed(2)}\,\text{CPU-s/s}\)
Why This Design Matters:
The exchange-queue separation enables location transparency - producers don’t know which consumers exist. You can: - Add new consumers by creating queues and bindings (zero code changes to producers) - Scale consumers independently (competing consumers pattern) - Implement fan-out routing (one message to many queues) without producer logic
65.4 AMQP Architecture Overview
AMQP’s architecture consists of three main components that work together to enable reliable, flexible message routing.
Publishes to exchanges (or directly to queues via the default exchange)
Does not need to know about consumers
Can publish to any exchange with appropriate credentials
2. Message Broker:
Central message routing and queuing system
Receives messages from producers
Routes to appropriate queues based on exchange rules and bindings
Delivers to consumers on demand or via push
Manages persistence, acknowledgments, and flow control
3. Consumer (Subscriber):
Application that receives messages
Subscribes to queues (not exchanges)
Processes messages asynchronously
Sends acknowledgments to confirm successful processing
65.4.2 Channel Multiplexing
AMQP supports multiple lightweight channels over a single TCP connection. This reduces connection overhead while allowing concurrent message streams.
Alternative View: Channel Multiplexing
This diagram shows AMQP’s channel multiplexing: a single TCP connection carries multiple lightweight channels, each independently managing different message streams.
Benefits of channel multiplexing:
Resource efficiency: One TCP connection can handle many message streams
Isolation: Errors on one channel don’t affect others
Parallelism: Multiple threads can use different channels concurrently
Reduced overhead: Avoids TCP connection setup costs for each stream
Queues are message buffers that store messages until consumed.
Key characteristics:
Message buffer (FIFO - First In, First Out)
Stores messages until consumed
Can be durable (survives broker restart)
Can be exclusive (single consumer, auto-delete)
Can be auto-delete (deleted when last consumer disconnects)
65.5.3 Bindings
Bindings are rules that connect exchanges to queues, defining the routing logic.
Key characteristics:
Rules connecting exchanges to queues
Defines routing logic (e.g., “route messages with key ‘sensor.temperature’ to queue ‘temp-data’”)
Can include additional arguments for headers exchanges
Multiple bindings can connect the same exchange to multiple queues
65.6 Exchange Types
Different exchange types provide flexible routing patterns for various messaging scenarios.
65.6.1 1. Direct Exchange
Direct exchanges route messages based on exact routing key matches.
Figure 65.3: Direct exchange routing based on exact routing key match
Characteristics:
Routes based on exact routing key match
Simple, efficient routing
Message goes to queue with binding key matching routing key exactly
Use cases:
Task assignment by type
Direct message delivery
Priority routing
65.6.2 2. Fanout Exchange
Fanout exchanges broadcast messages to all bound queues, ignoring routing keys.
Figure 65.4: Fanout exchange broadcasting messages to all bound queues
Characteristics:
Broadcasts to all bound queues
Ignores routing key completely
One-to-many delivery pattern
Use cases:
Notifications and announcements
System-wide events
Audit logging (copy to multiple destinations)
65.6.3 3. Topic Exchange
Topic exchanges route messages based on pattern matching with wildcards.
Figure 65.5: Topic exchange with wildcard pattern matching for flexible routing
Characteristics:
Routes based on pattern matching
Wildcards: * (exactly one word), # (zero or more words)
Routing key is dot-separated (e.g., “sensor.temperature.zone1”)
Use cases:
Flexible subscription patterns
IoT sensor data routing
Geographic or hierarchical routing
Pattern examples:
Pattern
Matches
Does NOT Match
sensor.#
sensor, sensor.temp, sensor.temp.zone1
other.temp
sensor.*
sensor.temp, sensor.humidity
sensor.temp.zone1
*.temperature.*
sensor.temperature.zone1
sensor.temp, zone.temperature
65.6.4 4. Headers Exchange
Headers exchanges route based on message header attributes rather than routing keys.
Characteristics:
Routes based on message header attributes
More flexible than routing key
Can match any header (x-match: all or any)
Use cases:
Complex routing logic
Routing based on message metadata
Content-based routing
AMQP Exchange Types Overview
Figure 65.6: The four AMQP exchange types provide increasingly flexible routing strategies. Direct exchanges offer simple point-to-point routing, fanout enables pub/sub broadcasting, topic exchanges support hierarchical pattern matching for IoT sensor data streams, and headers exchanges allow routing based on arbitrary message metadata.
exchangeInfo = {const info = {"Direct": {color:"#3498DB",description:"Exact match only. The routing key must exactly equal the binding key.",matchLogic:"Exact string comparison",queuesReceiving:1,bestFor:"Task queues, RPC, point-to-point messaging",performance:"Fastest (O(1) hash lookup)",example:`Routing key "${routingKey}" matches only a queue bound with exactly "${routingKey}".` },"Fanout": {color:"#E67E22",description:"Broadcast to ALL bound queues. The routing key is completely ignored.",matchLogic:"No matching needed - all queues receive",queuesReceiving: numQueues,bestFor:"Notifications, audit logging, event broadcasting",performance:"Fast (simple copy to N queues)",example:`Routing key "${routingKey}" is ignored. All ${numQueues} bound queues receive the message.` },"Topic": {color:"#16A085",description:"Wildcard pattern matching on dot-separated routing keys. Use * for one word, # for zero or more.",matchLogic:"Pattern matching with * and # wildcards",queuesReceiving:Math.min(numQueues,Math.max(1,Math.ceil(numQueues *0.6))),bestFor:"IoT sensor routing, geographic filtering, hierarchical data",performance:"Medium (pattern evaluation per binding)",example:`Routing key "${routingKey}" is matched against patterns like "${routingKey.split('.').slice(0,-1).join('.')}.*" or "${routingKey.split('.')[0]}.#".` },"Headers": {color:"#9B59B6",description:"Routes based on message header attributes instead of routing key. Supports x-match: all (AND) or any (OR).",matchLogic:"Header attribute comparison (AND/OR logic)",queuesReceiving:Math.min(numQueues,Math.max(1,Math.ceil(numQueues *0.4))),bestFor:"Complex multi-attribute routing, content-based decisions",performance:"Slowest (multi-attribute evaluation)",example:`Routing key "${routingKey}" is ignored. Headers like {sensor: "temperature", zone: "1"} determine routing.` } };return info[selectedExchange];}html`<div style="background: #f8f9fa; border-left: 4px solid ${exchangeInfo.color}; padding: 15px; margin-top: 15px; font-family: Arial, sans-serif;"> <h4 style="color: ${exchangeInfo.color}; margin-top: 0;">${selectedExchange} Exchange</h4> <p style="color: #2C3E50; margin: 8px 0; font-size: 14px;">${exchangeInfo.description}</p> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;"> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Match Logic</div> <div style="color: #2C3E50; font-size: 14px; font-weight: bold; margin-top: 4px;">${exchangeInfo.matchLogic}</div> </div> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Queues Receiving</div> <div style="color: ${exchangeInfo.color}; font-size: 24px; font-weight: bold; margin-top: 4px;">${exchangeInfo.queuesReceiving} / ${numQueues}</div> </div> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Performance</div> <div style="color: #2C3E50; font-size: 14px; font-weight: bold; margin-top: 4px;">${exchangeInfo.performance}</div> </div> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Best For</div> <div style="color: #2C3E50; font-size: 14px; margin-top: 4px;">${exchangeInfo.bestFor}</div> </div> </div> <div style="margin-top: 12px; padding: 10px; background: white; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase; margin-bottom: 4px;">Routing Example</div> <div style="color: #2C3E50; font-size: 13px;">${exchangeInfo.example}</div> </div> <div style="margin-top: 12px; display: flex; gap: 4px; align-items: center;">${Array.from({length: numQueues}, (_, i) => {const receiving = i < exchangeInfo.queuesReceiving;return`<div style="flex: 1; height: 32px; border-radius: 4px; background: ${receiving ? exchangeInfo.color:'#e0e0e0'}; display: flex; align-items: center; justify-content: center; color: ${receiving ?'white':'#7F8C8D'}; font-size: 11px; font-weight: bold;">Q${i +1}${receiving ?' ✓':''}</div>`; }).join('')} </div> <div style="color: #7F8C8D; font-size: 11px; margin-top: 4px; text-align: center;">Queue delivery visualization (green = receives message)</div></div>`
65.7 IoT Sensor Data Routing Example
A practical example demonstrating topic exchange routing for IoT sensor data.
Figure 65.7: IoT sensor data routing via topic exchange with pattern-based subscriptions
Routing analysis:
Sensor
Routing Key
Temperature Queue
Zone 1 Queue
Archive Queue
Temperature (zone1)
sensor.temp.zone1
Yes (sensor.temp.#)
Yes (sensor.*.zone1)
Yes (sensor.#)
Humidity (zone2)
sensor.humidity.zone2
No
No
Yes (sensor.#)
Pressure (zone1)
sensor.pressure.zone1
No
Yes (sensor.*.zone1)
Yes (sensor.#)
This demonstrates how a single message can be routed to multiple queues based on pattern matching, enabling efficient data distribution without producer knowledge of consumers.
Figure 65.8: A complete AMQP topology demonstrates the separation of concerns between message production, intelligent routing, and consumption. Producers remain decoupled from queue topology while exchanges implement the routing logic, enabling dynamic consumer scaling and flexible message distribution patterns.
Interactive: AMQP Exchange Types Animation
65.8 Real-World Case Study: Industrial IoT with AMQP Exchange Routing
Scenario: An automotive assembly plant monitors 1,200 robots across 6 production lines. Each robot publishes diagnostic data including vibration, temperature, motor current, and error codes. Three backend systems consume this data differently.
Message volume:
1,200 robots x 4 sensor types x 1 reading/second = 4,800 messages/second
Peak during shift change: 7,200 msg/s (1.5x burst factor)
Average message size: 200 bytes
Consumer requirements:
System
Needs
Latency Requirement
Real-time dashboard
All data from all robots
< 500 ms
Predictive maintenance
Only vibration + motor current
< 5 seconds
Quality compliance
Only error codes + temperature
< 30 seconds
Exchange design decision:
Option A: Three direct exchanges (one per consumer)
Producers must publish each message 3 times to different exchanges
Network load: 4,800 x 3 = 14,400 publishes/second
Producer CPU overhead: 3x serialization per reading
Option B: One topic exchange with pattern matching
Network load: 4,800 publishes/second (each message published once)
Broker routes to 1-3 queues per message based on bindings
Option C: One fanout exchange
All three queues receive all 4,800 messages/second
Simple, but each consumer filters locally, wasting bandwidth
Each consumer processes 4,800 msg/s but discards 50-75% of them
Bandwidth comparison:
Approach
Publish Bandwidth
Total Queue Bandwidth
Wasted Processing
Direct (3x)
2.88 MB/s
960 KB/s per queue
None
Topic (1x)
960 KB/s
960 KB/s (dashboard), 480 KB/s (each filtered)
None
Fanout (1x)
960 KB/s
960 KB/s x 3 = 2.88 MB/s
50-75% per filtered consumer
Decision: Topic exchange. Producers publish once (saving CPU and bandwidth), the broker handles routing (its core job), and each consumer receives only relevant messages. The broker’s pattern matching adds < 0.1 ms latency per message – negligible for all three consumers’ requirements.
Channel multiplexing benefit: Each of the 1,200 robots uses a single TCP connection with 4 channels (one per sensor type). Without multiplexing, the plant would need 4,800 TCP connections instead of 1,200 – a 4x reduction in TCP handshake overhead and socket resource consumption on the broker.
Interactive Calculator: Exchange Type Bandwidth Comparison
Worked Example: Industrial IoT Message Routing with Topic Exchange
Scenario: A factory floor has 50 CNC machines publishing status updates to an AMQP broker. Three backend systems need different subsets of this data: - Maintenance system: Only machine errors and warnings (routing key pattern: factory.*.error, factory.*.warning) - Production dashboard: All status updates from all machines (routing key pattern: factory.#) - Machine learning analytics: Only operational metrics from Line 3 machines (routing key pattern: factory.line3.*.metrics)
Each CNC machine publishes to routing keys like: - factory.line1.cnc001.metrics - factory.line3.cnc042.error - factory.line2.cnc015.warning
Calculating message distribution:
Publisher sends 1 message with routing key factory.line3.cnc042.error:
Queue
Binding Pattern
Match?
Reason
Maintenance Queue
factory.*.error
Yes
* matches line3, .error exact match
Maintenance Queue
factory.*.warning
No
.error != .warning
Dashboard Queue
factory.#
Yes
# matches all remaining words
Analytics Queue
factory.line3.*.metrics
No
.error != .metrics
Result: This single PUBLISH reaches 2 queues (Maintenance + Dashboard). The broker routing eliminates 67% of unnecessary deliveries compared to fanout (which would send to all 3 queues).
Bandwidth savings: 53% reduction in broker-to-consumer traffic
Decision Framework: Choosing the Right AMQP Exchange Type
Use this table to select the optimal exchange type for your messaging pattern:
Requirement
Direct
Fanout
Topic
Headers
One routing key → one queue
✓ Best
✗
△ Overkill
✗
Broadcast to all queues
✗
✓ Best
△ Use #
△ Complex
Pattern matching (wildcards)
✗
✗
✓ Best
△ Possible
Multi-attribute routing
✗
✗
△ Limited
✓ Best
Performance (10K msg/s)
Highest
High
Medium
Lower
Routing complexity
Lowest
Lowest
Medium
Highest
Decision flowchart:
Do all consumers need every message? → Yes: Fanout exchange
Is routing based on a single exact key? → Yes: Direct exchange
Do you need wildcard patterns (e.g., sensor.*.zone1)? → Yes: Topic exchange
Does routing depend on multiple message properties? → Yes: Headers exchange
Example decisions:
Scenario
Exchange Type
Reasoning
Task queue for image processing
Direct
Each task type (thumbnail, resize, filter) routes to dedicated queue
System-wide audit logging
Fanout
All log aggregators receive every message
IoT sensor data (e.g., sensor.temp.floor3)
Topic
Dashboard subscribes to sensor.temp.#, HVAC to sensor.*.floor3
Email routing (priority=high AND region=us-west)
Headers
Multiple attributes, complex AND/OR logic
Anti-pattern warning: Avoid using topic exchange with no wildcards (e.g., binding sensor.temp.zone1 exactly) – this wastes the pattern matcher. Use direct exchange instead for 3x faster routing.
Common Mistake: Publishing to the Default Exchange Without Understanding Routing
The Error: Developers new to AMQP use exchange='' (the default exchange) thinking it simplifies publishing, but they create messages that route directly to a queue by name instead of using exchange-based routing logic.
Why It Happens: Every AMQP broker provides a nameless default exchange that routes messages directly to queues when routing_key=queue_name. This appears to work initially, tightly coupling the publisher to specific queue names.
Real-World Impact: A logistics company built 200 microservices that published to the default exchange. When they needed to add a second consumer (analytics service) to existing shipment events, they had to: 1. Modify 200 publishers to publish twice (once to original queue, once to analytics queue) 2. Deploy updated code to 200 services 3. Result: 3-week rollout, 18 incidents due to missed duplicate publishes
The Fix:
Bad (default exchange, tight coupling):
# Publisher knows queue name -- violates decoupling principlechannel.basic_publish( exchange='', # Default exchange routing_key='shipment_events_queue', # Direct queue name body=json.dumps({"shipment_id": "SH-42", "status": "delivered"}))
Good (topic exchange, decoupled):
# Publisher only knows routing key semantic meaningchannel.basic_publish( exchange='logistics_exchange', routing_key='shipment.delivered', # Business event, not infrastructure body=json.dumps({"shipment_id": "SH-42", "status": "delivered"}))# Add new consumer with zero publisher changes:channel.queue_bind( exchange='logistics_exchange', queue='analytics_queue', routing_key='shipment.#'# Receives all shipment events)
Key Numbers:
With default exchange: Adding a 2nd consumer required modifying 200 publishers (200 × 2 hours dev + testing = 400 hours)
With topic exchange: Adding a 2nd consumer required 1 queue binding command (5 minutes)
Prevention: Declare and use a named exchange from day one, even if you only have one consumer. The 30 seconds of initial configuration saves months of refactoring later.
Common Pitfalls
1. Declaring Exchanges and Queues in Application Code
Hardcoding exchange/queue declaration in the publisher causes failure when the producer starts before the consumer creates the queue — messages are silently discarded. Separate infrastructure provisioning (declare exchanges/queues at deployment time via management API or IaC) from application code.
2. Using Default Exchange for All Routing
The default (nameless) AMQP exchange only supports exact queue-name routing — every producer must know every consumer’s queue name, creating tight coupling. Use named exchanges with appropriate types (topic for IoT telemetry) to enable dynamic routing without producer changes.
3. Opening One Channel Per Thread Without Pooling
AMQP channels are lightweight, but creating thousands per second degrades broker performance. Use a channel pool (max 10-50 channels per connection) and return channels after use — connection/channel churn is a top cause of broker CPU spikes in high-throughput IoT deployments.
Label the Diagram
💻 Code Challenge
Order the Steps
Match the Concepts
65.9 Summary
This chapter covered the core architecture of AMQP messaging:
Core Components: Explained producer, broker, and consumer roles with message routing through exchanges to queues
Channel Multiplexing: Demonstrated how multiple logical channels share a single TCP connection for efficiency
AMQP 0-9-1 Model: Described the exchange-binding-queue architecture that enables flexible routing
Exchange Types: Configured direct (exact match), fanout (broadcast), topic (pattern matching), and headers (attribute-based) exchanges
Binding Rules: Designed routing rules connecting exchanges to queues with routing keys and patterns
IoT Application: Applied topic exchange patterns for sensor data routing scenarios
65.10 Concept Relationships
Understanding how AMQP concepts interconnect helps you design effective messaging topologies:
sensor.temperature.zone1.* (matches all machines in zone1)
sensor.temperature.# (matches all temperature sensors)
sensor.# (matches all sensor types)
# (matches everything)
Exchange Type Selection Decision Tree:
Need to route to all consumers? → Fanout
Need pattern-based filtering? → Topic
Need exact routing key matching? → Direct
Need header attribute matching? → Headers
Contrast with MQTT:
AMQP: Client publishes to exchange → exchange routes to queues → consumer reads from queue (3-step, server-side routing)
MQTT: Client publishes to topic → broker matches topic to subscriptions → broker pushes to clients (2-step, client-side filtering)
Try It: Queue Configuration Builder
Show code
viewof queueName = Inputs.text({value:"temperature_alerts",label:"Queue name:",placeholder:"e.g. temperature_alerts"})viewof queueDurable = Inputs.radio( ["Durable (survives restart)","Transient (memory only)"], {value:"Durable (survives restart)",label:"Durability:"})viewof queueExclusive = Inputs.checkbox( ["Exclusive (single consumer, auto-delete on disconnect)"], {label:"Access mode:"})viewof queueMaxLength = Inputs.range([0,100000], {value:10000,step:1000,label:"Max queue length (messages):",format: x => x ===0?"Unlimited": x.toLocaleString()})viewof queueTTL = Inputs.range([0,3600000], {value:60000,step:5000,label:"Message TTL (ms):",format: x => x ===0?"No expiry": x >=60000?`${(x /60000).toFixed(1)} min`:`${(x /1000).toFixed(0)} sec`})viewof msgRate = Inputs.range([10,5000], {value:500,step:10,label:"Expected msg/sec:",format: x => x.toLocaleString()})
Show code
queueIsDurable = queueDurable ==="Durable (survives restart)"queueIsExclusive = queueExclusive.length>0estimatedMemoryMB = (queueMaxLength *280) / (1024*1024)timeToFill = queueMaxLength >0? (queueMaxLength / msgRate) :InfinitydiskWriteKBs = queueIsDurable ? (msgRate *280/1024) :0html`<div style="background: #f8f9fa; border-left: 4px solid #9B59B6; padding: 15px; margin-top: 15px; font-family: Arial, sans-serif;"> <h4 style="color: #2C3E50; margin-top: 0;">Queue Configuration: <code>${queueName}</code></h4> <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-top: 12px;"> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Memory Estimate</div> <div style="color: #9B59B6; font-size: 22px; font-weight: bold; margin-top: 4px;">${estimatedMemoryMB.toFixed(1)} MB</div> <div style="color: #7F8C8D; font-size: 11px;">At max capacity (${queueMaxLength >0? queueMaxLength.toLocaleString() :'unlimited'} msgs)</div> </div> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Time to Fill</div> <div style="color: ${timeToFill <30?'#E74C3C':'#16A085'}; font-size: 22px; font-weight: bold; margin-top: 4px;">${timeToFill ===Infinity?'N/A': timeToFill <60? timeToFill.toFixed(1) +'s': (timeToFill /60).toFixed(1) +' min'}</div> <div style="color: #7F8C8D; font-size: 11px;">At ${msgRate.toLocaleString()} msg/sec (no consumers)</div> </div> <div style="background: white; padding: 10px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 11px; text-transform: uppercase;">Disk I/O</div> <div style="color: #E67E22; font-size: 22px; font-weight: bold; margin-top: 4px;">${queueIsDurable ? diskWriteKBs.toFixed(0) +' KB/s':'None'}</div> <div style="color: #7F8C8D; font-size: 11px;">${queueIsDurable ?'Write-ahead log overhead':'Transient: no disk writes'}</div> </div> </div> <div style="margin-top: 12px; background: white; padding: 12px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #2C3E50; font-weight: bold; font-size: 13px; margin-bottom: 8px;">Generated RabbitMQ Declaration</div> <pre style="background: #2C3E50; color: #e0e0e0; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto; margin: 0;">channel.queue_declare( queue='${queueName}', durable=${queueIsDurable ?'True':'False'}, exclusive=${queueIsExclusive ?'True':'False'}, auto_delete=${queueIsExclusive ?'True':'False'}, arguments={${queueMaxLength >0?`\n 'x-max-length': ${queueMaxLength},`:''}${queueTTL >0?`\n 'x-message-ttl': ${queueTTL},`:''} })</pre> </div> <div style="margin-top: 10px; padding: 8px; background: ${timeToFill <30?'#fdf2f2': queueIsExclusive ?'#fef9e7':'#e8f8f5'}; border-radius: 4px; font-size: 12px; color: #2C3E50;">${timeToFill <30?'<strong>Warning:</strong> Queue fills in under 30 seconds at current rate. Consider increasing max-length, adding consumers, or reducing message rate.': queueIsExclusive?'<strong>Note:</strong> Exclusive queues are deleted when the consumer disconnects. Good for temporary reply queues in RPC patterns.': queueIsDurable?'<strong>Recommendation:</strong> Durable queue with TTL provides good durability with automatic cleanup of stale messages.':'<strong>Note:</strong> Transient queue will lose messages on broker restart. Suitable for real-time dashboards where stale data is not useful.'} </div></div>`
Hands-on exercises to practice AMQP exchange routing concepts:
65.14.1 Exercise 1: Design Topic Exchange Routing
Scenario: Smart building with 50 rooms across 5 floors, each room has 3 sensor types (temperature, humidity, occupancy).
Task: Design routing keys and binding patterns for: 1. HVAC system needs all temperature sensors 2. Security system needs all occupancy sensors 3. Floor 3 manager needs all sensors on floor 3 4. Energy dashboard needs everything
Solution Template:
# Routing key format: building.floor{N}.room{ID}.{sensor_type}# Example: building.floor3.room15.temperature# HVAC binding:channel.queue_bind( queue='hvac_queue', exchange='building_sensors', routing_key='building.*.*.temperature'# Your pattern here)# Security binding:# Exercise: Write binding for occupancy sensors# Floor 3 manager binding:# Exercise: Write binding for floor 3 only# Energy dashboard binding:# Exercise: Write binding for all sensors
Expected Result:
Message building.floor3.room15.temperature should route to HVAC, Floor 3 manager, and Energy dashboard (3 queues)
Message building.floor1.room05.occupancy should route to Security and Energy dashboard (2 queues)
65.14.2 Exercise 2: Calculate Message Distribution
Given:
50 rooms × 3 sensors = 150 publishers
Each sensor publishes 1 msg/minute
4 consumers with different binding patterns (from Exercise 1)
Tasks:
Calculate messages/hour for each queue
Estimate queue memory with 200-byte average message size
Determine if fanout exchange would waste bandwidth (compare to topic)
65.14.3 Exercise 3: Debug Routing Problem
Symptom: Temperature dashboard receives no messages, but messages are being published.
debugResult = {const rk = debugRoutingKey.trim();const bk = debugBindingKey.trim();if (debugExchangeType ==="direct") {const matches = rk === bk;return { matches,reason: matches?`Exact match: "${rk}" equals "${bk}".`:`No match: "${rk}" does not exactly equal "${bk}".`,rkParts: rk.split('.'),bkParts: bk.split('.'),partComparison:null }; }// Topic matching with detailed per-part comparisonconst rkParts = rk.split('.');const bkParts = bk.split('.');const comparison = [];let ri =0, bi =0;let matched =true;let failReason ="";while (ri < rkParts.length&& bi < bkParts.length) {if (bkParts[bi] ==='#') {for (let k = ri; k < rkParts.length; k++) { comparison.push({rk: rkParts[k],bk: (k === ri ?'#':'(consumed by #)'),status:'match',note:'# matches zero or more words'}); } ri = rkParts.length; bi = bkParts.length;break; } elseif (bkParts[bi] ==='*') { comparison.push({rk: rkParts[ri],bk:'*',status:'match',note:'* matches exactly one word'}); ri++; bi++; } elseif (bkParts[bi] === rkParts[ri]) { comparison.push({rk: rkParts[ri],bk: bkParts[bi],status:'match',note:'Exact word match'}); ri++; bi++; } else { comparison.push({rk: rkParts[ri],bk: bkParts[bi],status:'fail',note:`"${rkParts[ri]}" != "${bkParts[bi]}"`}); matched =false; failReason =`Word mismatch at position ${ri +1}: "${rkParts[ri]}" does not match "${bkParts[bi]}".`; ri++; bi++; } }if (matched && ri < rkParts.length) { matched =false; failReason =`Routing key has ${rkParts.length- ri} extra word(s) that the pattern does not cover.`;for (let k = ri; k < rkParts.length; k++) { comparison.push({rk: rkParts[k],bk:'(none)',status:'fail',note:'No matching pattern element'}); } }if (matched && bi < bkParts.length&& bkParts[bi] !=='#') { matched =false; failReason =`Binding pattern has ${bkParts.length- bi} extra element(s) beyond the routing key.`; }return {matches: matched,reason: matched ?'All parts matched successfully.': failReason, rkParts, bkParts,partComparison: comparison };}html`<div style="background: #f8f9fa; border-left: 4px solid #E67E22; padding: 15px; margin-top: 15px; font-family: Arial, sans-serif;"> <h4 style="color: #2C3E50; margin-top: 0;">Routing Debug Analysis</h4> <div style="padding: 12px; margin-bottom: 12px; background: ${debugResult.matches?'#e8f8f5':'#fdf2f2'}; border: 2px solid ${debugResult.matches?'#16A085':'#E74C3C'}; border-radius: 4px; text-align: center;"> <div style="font-size: 20px; font-weight: bold; color: ${debugResult.matches?'#16A085':'#E74C3C'};">${debugResult.matches?'MATCH - Message Delivered':'NO MATCH - Message Not Routed'} </div> <div style="font-size: 13px; color: #2C3E50; margin-top: 4px;">${debugResult.reason}</div> </div>${debugResult.partComparison?` <div style="background: white; padding: 12px; border-radius: 4px; border: 1px solid #e0e0e0;"> <div style="color: #7F8C8D; font-size: 12px; font-weight: bold; margin-bottom: 8px;">WORD-BY-WORD COMPARISON (${debugExchangeType} exchange)</div> <table style="width: 100%; border-collapse: collapse; font-size: 13px;"> <tr style="background: #2C3E50; color: white;"> <th style="padding: 6px 8px; text-align: left;">Position</th> <th style="padding: 6px 8px; text-align: left;">Routing Key</th> <th style="padding: 6px 8px; text-align: left;">Binding Pattern</th> <th style="padding: 6px 8px; text-align: left;">Result</th> </tr>${debugResult.partComparison.map((c, i) =>` <tr style="background: ${c.status==='match'?'#f0faf8':'#fef5f5'}; border-bottom: 1px solid #e0e0e0;"> <td style="padding: 6px 8px; color: #7F8C8D;">${i +1}</td> <td style="padding: 6px 8px; font-family: monospace; font-weight: bold;">${c.rk}</td> <td style="padding: 6px 8px; font-family: monospace; color: ${c.bk==='*'|| c.bk==='#'|| c.bk==='(consumed by #)'?'#E67E22':'#2C3E50'}; font-weight: bold;">${c.bk}</td> <td style="padding: 6px 8px; color: ${c.status==='match'?'#16A085':'#E74C3C'}; font-size: 12px;">${c.note}</td> </tr> `).join('')} </table> </div>${!debugResult.matches?'<div style="margin-top: 10px; padding: 8px; background: #fef9e7; border-radius: 4px; font-size: 12px; color: #2C3E50; border-left: 4px solid #E67E22;"><strong>Debugging tip:</strong> Check for typos or abbreviation mismatches (e.g., "temperature" vs "temp"). Use <code>#</code> at the end of a pattern to match variable-depth keys, or <code>*</code> to match exactly one word at a specific position.</div>':''} `:` <div style="background: white; padding: 12px; border-radius: 4px; border: 1px solid #e0e0e0; font-size: 13px; color: #7F8C8D;"> Direct exchange uses exact string comparison. No word-by-word analysis needed. </div> `}</div>`
65.14.4 Exercise 4: Implement Competing Consumers
Objective: Set up 3 worker processes that share a single queue for load distribution.
Requirements:
Each worker processes messages independently
If one worker crashes, others continue processing
Ensure fair distribution (no worker hoarding)
Code skeleton:
# Exercise: Set prefetch count for fair distributionchannel.basic_qos(prefetch_count=???)def process_order(ch, method, properties, body):# Exercise: Process order# Exercise: Send acknowledgment only after successpasschannel.basic_consume( queue='orders', on_message_callback=process_order, auto_ack=??? # True or False?)
Questions:
Should you use auto_ack=True or False? Why?
What prefetch count ensures fair distribution?
What happens if a worker crashes mid-processing with auto_ack=True?
Next Steps: Try these exercises with a local RabbitMQ instance. See AMQP Implementations and Labs for broker setup instructions.