RabbitMQ Publish Subscribe Pattern
The publish-subscribe (pub/sub) pattern lets one producer broadcast a message to multiple independent consumers simultaneously. Each consumer receives its own copy of the message. Unlike work queues where one worker handles one task, in pub/sub every subscriber gets the full message at the same time.
The TV Channel Analogy
A TV broadcast station (publisher) transmits a live show. Every television (subscriber) tuned to that channel receives the same broadcast simultaneously. Adding a new TV does not affect the broadcast station — it just starts receiving. Removing a TV has no impact on other viewers.
Pub/Sub vs Work Queues
Work Queue Pattern:
[Producer] --> [Queue] --> Worker A OR Worker B
(only ONE worker handles each message)
Pub/Sub Pattern:
[Producer] --> [Fanout Exchange] --> Queue A --> Subscriber A
--> Queue B --> Subscriber B
--> Queue C --> Subscriber C
(ALL subscribers handle each message independently)
How Pub/Sub Works in RabbitMQ
A fanout exchange powers the pub/sub pattern. Each subscriber creates its own dedicated queue and binds it to the same fanout exchange. When the producer publishes one message, the exchange delivers a copy to every bound queue. Each subscriber reads from its own queue independently.
[News Publisher]
|
[Fanout Exchange: "news-updates"]
| | |
[Queue 1] [Queue 2] [Queue 3]
| | |
[Mobile [Web App [Email
Notifier] Updater] Digest]
Subscriber Independence
Each subscriber works at its own pace. If the Email Digest service is slow, it simply processes messages from its queue at whatever speed it can. This does not affect the Mobile Notifier or the Web App Updater — they read from their own queues. A slow subscriber never blocks a fast one.
Similarly, if the Email Digest service crashes, its queue holds messages until the service restarts. The other subscribers are completely unaffected.
Temporary vs Permanent Subscriptions
Temporary Subscription (Exclusive Queue)
The subscriber creates an exclusive, auto-delete queue. When the subscriber disconnects, the queue and all its unread messages disappear. This is suitable for real-time feeds where missed events during downtime are acceptable (e.g., live scoreboards, stock tickers).
result = channel.queue_declare(queue='', exclusive=True) queue_name = result.method.queue channel.queue_bind(exchange='live-feed', queue=queue_name)
Permanent Subscription (Durable Queue)
The subscriber creates a named, durable queue. Messages accumulate while the subscriber is offline and are delivered when it reconnects. This is suitable for business events where no message should be missed (e.g., invoice events, audit logs).
channel.queue_declare(queue='invoice-subscriber-queue', durable=True) channel.queue_bind(exchange='billing-events', queue='invoice-subscriber-queue')
Real-World Example: User Registration System
A user registration service publishes one event when a new user signs up. Five different systems care about this event:
- Welcome Email Service — sends the welcome email
- Analytics Service — records the signup for reporting
- CRM Service — creates a new customer record
- Fraud Detection Service — checks the new account for suspicious signals
- Onboarding Service — starts the onboarding workflow
[Registration Service]
|
[Fanout Exchange: "user.registered"]
| | | | |
[Q-1] [Q-2] [Q-3] [Q-4] [Q-5]
| | | | |
[Email] [Analytics][CRM] [Fraud] [Onboard]
The Registration Service publishes one message. All five systems receive it independently. No service blocks another. Each service can fail and recover without affecting the others.
Filtering in Pub/Sub with Topic Exchange
A pure fanout exchange sends every message to every subscriber. When subscribers need to receive only a subset of messages, use a topic exchange instead of fanout. Each subscriber binds its queue with a pattern that filters which messages it cares about.
[Topic Exchange: "product-events"] Subscriber A binds: "product.created" Subscriber B binds: "product.#" (all product events) Subscriber C binds: "product.deleted" Message routing_key = "product.created": --> Subscriber A ✓ --> Subscriber B ✓ --> Subscriber C ✗ Message routing_key = "product.deleted": --> Subscriber A ✗ --> Subscriber B ✓ --> Subscriber C ✓
Setting Up Pub/Sub in Python
Publisher:
channel.exchange_declare(exchange='news-updates', exchange_type='fanout') channel.basic_publish(exchange='news-updates', routing_key='', body='Breaking: New feature launched!')
Subscriber:
channel.exchange_declare(exchange='news-updates', exchange_type='fanout')
result = channel.queue_declare(queue='', exclusive=True)
channel.queue_bind(exchange='news-updates', queue=result.method.queue)
def callback(ch, method, properties, body):
print(f"Received: {body.decode()}")
channel.basic_consume(queue=result.method.queue, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
Summary
The publish-subscribe pattern lets a single producer send one message that multiple independent consumers each receive a copy of. A fanout exchange powers pure pub/sub. A topic exchange powers filtered pub/sub where subscribers choose which events to receive. Temporary exclusive queues suit real-time feeds. Durable named queues suit business events where no message should be missed. Pub/sub decouples producers from consumers completely — the publisher does not know or care how many subscribers exist.
