Redis Pub/Sub Publish Subscribe
Learn Redis pub/sub to decouple services. This messaging & queues lesson shows how to publish and subscribe with Redis pub/sub, with hands-on examples and troubleshooting.
Focus: publish and subscribe with redis pub/sub
Ever built a feature where one service needs to tell several others, "Hey, something just happened," without waiting for a reply? You could hammer a database with polling, or wire services together with tight HTTP calls that break the moment one of them goes down. There's a simpler, battle-tested pattern: publish and subscribe with Redis pub/sub. This lesson strips away the jargon and shows you how to use Redis as a lightning-fast message bus — so you can decouple services, react to events in real time, and stop writing brittle point-to-point integrations.
The problem this lesson solves
Modern backends are rarely a single process. You'll have an API service, a worker that processes jobs, a notification service, maybe a WebSocket gateway — and they all need to know when important things happen. The naive approach is synchronous HTTP calls: Service A calls Service B's endpoint every time something changes. That works at small scale, but it breaks down fast:
- If Service B is down, Service A's request fails — so you add retries, which pile up.
- If Service C also needs to know, you add another HTTP call, and another.
- Every new consumer means editing Service A's code.
- Your system becomes a spiderweb of hardcoded dependencies.
Polling a database has the same problems, plus it wastes resources and adds latency.
Redis pub/sub solves this by introducing a message broker in the middle. Publishers just fire a message into a channel; subscribers that are interested in that channel get a copy — instantly. Publishers don't know who's listening, and subscribers don't know who published. This decoupling is the core win.
Core concept / mental model
Think of a radio station.
- The station (the publisher) broadcasts a signal on a specific frequency (the channel).
- Anyone with a radio tuned to that frequency (the subscriber) hears the broadcast in real time.
- The station doesn't know how many radios are listening, and the radios don't know who else is listening.
- If no one tunes in, the broadcast still happens — it just fades into silence.
Redis pub/sub works exactly like that. In Redis terms:
- Publisher — any client that sends a message to a channel using
PUBLISH channel message. - Subscriber — a client that has expressed interest in one or more channels using
SUBSCRIBE channel. - Channel — a named conduit for messages, e.g.,
order.created,user.updated. - Message — a string payload (often JSON).
Here's the critical distinction from queues: pub/sub is fire-and-forget. A published message is delivered to currently connected subscribers. If a subscriber is offline, it misses the message — there's no replay, no persistence. That's a feature, not a bug. You'll use it for live notifications, not for guaranteed job processing.
A picture in words
[Publisher A] -- PUBLISH order.created --> [Redis] --> SUBSCRIBE -- [Service X]
\-- SUBSCRIBE -- [Service Y]
[Publisher B] -- PUBLISH user.updated --> [Redis] --> SUBSCRIBE -- [Service Z]
Each subscriber listens to specific channels. Redis fans messages out to all subscribers of that channel in O(N) time (where N is subscriber count) — incredibly fast.
How it works step by step
Let's trace the lifecycle of a message using Redis commands, then we'll translate that into Python.
- A subscriber connects to Redis and issues
SUBSCRIBE channel. From that moment, Redis keeps a connection open for that client dedicated to receiving messages. - A publisher connects (any Redis client) and issues
PUBLISH channel payload. The payload is a string — typically JSON or plain text. - Redis looks up all subscribers for that channel and pushes the message to each one, in the order they subscribed.
- Each subscriber receives the message on its connection — as a list-like structure:
['message', 'channel', 'payload']in raw Redis protocol, or as a dict in high-level clients likeredis-py. - The subscriber handles the message — e.g., updates a cache, sends an email, or triggers a WebSocket broadcast.
- No acknowledgment is needed. The message is gone after delivery. If a subscriber crashes mid-processing, that message is lost.
There's also pattern subscriptions with PSUBSCRIBE channel.* — Redis matches channels using glob-style patterns, so you can subscribe to order.* and get order.created, order.cancelled, etc. That's handy for grouping.
One more critical rule: Redis pub/sub messages are not persisted. If Redis restarts, pending messages are gone. For durable delivery, you'd look at Redis Streams or a proper queue — which we'll compare later.
Hands-on walkthrough
Time to get your hands dirty. We'll use Python's redis library (pip install redis). Start a Redis server locally (redis-server) or use a cloud instance.
Example 1: Raw pub/sub with the CLI (just to see it work)
Open two terminals.
Terminal 1 (subscriber):
redis-cli subscribe news
Terminal 2 (publisher):
redis-cli publish news "Hello, world!"
You'll see in Terminal 1:
1) "subscribe"
2) "news"
3) (integer) 1
1) "message"
2) "news"
3) "Hello, world!"
That's the core — a message was published to channel news and delivered to the subscriber.
Example 2: A Python subscriber
Now let's build a real subscriber in Python. This script connects, subscribes to two channels, and processes messages as they arrive.
import json
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
ps = r.pubsub()
ps.subscribe('order.created', 'user.updated') # subscribe to multiple channels
print("Subscribed. Waiting for messages...")
for message in ps.listen():
if message['type'] != 'message':
continue # skip subscribe/unsubscribe confirmation messages
channel = message['channel']
data = message['data']
print(f"Received on {channel}: {data}")
# In a real app, you'd dispatch to a handler based on channel
if channel == 'order.created':
order = json.loads(data) # assume data is JSON
print(f"New order {order['id']} — sending confirmation...")
elif channel == 'user.updated':
print("User updated — invalidating cache...")
Example 3: A Python publisher
Run this in another terminal — it publishes a couple of events.
import json
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Publish an order event
order = {"id": 1001, "user": "alice", "total": 59.99}
r.publish('order.created', json.dumps(order))
print("Published order.created")
# Publish a user update
data = {"user_id": 42, "email": "alice@example.com"}
r.publish('user.updated', json.dumps(data))
print("Published user.updated")
Expected output in the subscriber terminal:
Received on order.created: {"id": 1001, "user": "alice", "total": 59.99}
New order 1001 — sending confirmation...
Received on user.updated: {"user_id": 42, "email": "alice@example.com"}
User updated — invalidating cache...
Example 4: Pattern subscriptions
Let's use PSUBSCRIBE to catch all audit events.
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
ps = r.pubsub()
ps.psubscribe('audit.*')
print("Listening for audit events...")
for message in ps.listen():
if message['type'] == 'pmessage':
pattern = message['pattern']
channel = message['channel']
data = message['data']
print(f"Audit: {channel} -> {data}")
Publish to any audit.* channel and watch it appear. This is powerful when you want to add new event types without redeploying subscribers.
Pro tip: Use
decode_responses=Trueinredis-py— otherwise you'll get bytes and have to decode manually everywhere.Pro tip: In production, run the subscriber loop in its own process or thread. Don't block your main event loop on
listen().
Compare options / when to choose what
Redis pub/sub is one of several messaging patterns. Let's see how it stacks up against its closest cousins.
| Feature | Redis Pub/Sub | Redis Streams | Celery / RabbitMQ (Work Queue) |
|---|---|---|---|
| Delivery | Fire-and-forget, no persistence | Persistent, replayable | Durable, with acknowledgments |
| Consumer model | Fan-out (all subscribers get every message) | Consumer groups (competing consumers) | Competing consumers typically |
| Offline subscriber? | Misses messages | Can read from last ID | Messages wait in queue |
| Latency | Sub-millisecond | Sub-millisecond | Milliseconds (depends) |
| Best for | Live events, broadcasting, real-time updates | Event sourcing, task queues with history | Reliable task execution |
| Complexity | Very low | Moderate | Higher (broker, workers) |
When to choose Redis pub/sub:
- You need real-time fan-out — every subscriber gets each event.
- You don't need replay or durability.
- You want the simplest possible broker (just Redis, no extra infra).
- Your subscribers are always online (e.g., WebSocket servers).
When to avoid it:
- You need guaranteed processing (pub/sub doesn't know if a subscriber handled a message).
- You need to recover lost messages after a crash.
- You have slow consumers — pub/sub drops messages for slow subscribers (messages aren't buffered). In that case, use Streams or a queue.
Troubleshooting & edge cases
Here are the usual pain points and how to fix them.
"My subscriber isn't receiving messages"
- Check Redis connectivity —
redis-cli pingshould returnPONG. - Is the subscriber actually subscribed? Look for the
subscribeconfirmation in the loop output. If you only seesubscribemessages, your filter onmessage['type']is correct. - Are you publishing to the same channel name? Channel names are case-sensitive.
Order.Createdis notorder.created. - Did the subscriber connect after the publish? Remember: if no one is listening, the message evaporates. Subscribe first, then publish.
"The subscriber misses messages when publishing fast"
Redis pub/sub is not a message buffer. If a subscriber can't keep up, messages are dropped (they're not queued). Monitor your subscriber's processing speed. If you see drops, switch to Redis Streams or a proper queue.
"Connection gets disconnected"
- Redis has a
timeoutconfig; if the subscriber idle for too long, Redis may disconnect it. Set a longer timeout or use a connection that sends periodic pings. - Network hiccups? The
redis-pyclient will retry, but you may miss messages during the gap. For critical apps, use reconnection logic with persistent storage.
"redis.exceptions.ResponseError: UNBLOCKED client unblocked via CLIENT UNBLOCK"
That's a Redis internal message — usually seen when using CLIENT UNBLOCK or when a blocked connection is interrupted. In practice, just ignore it or make sure you're not manually blocking the connection.
"Memory usage blows up"
Pub/sub itself doesn't store messages, but each subscriber connection holds a receive buffer. If you have thousands of subscribers sending huge payloads, memory per connection grows. Keep payloads small (e.g., send just an ID, not the full object).
What you learned & what's next
In this lesson, you learned the core idea behind Redis pub/sub: a decoupled, real-time messaging pattern where publishers and subscribers interact through channels without knowing each other. You completed a hands-on exercise — building a Python subscriber and publisher, and even used pattern subscriptions. You also compared pub/sub to queues and streams, and you can now troubleshoot common issues like missed messages and connection drops.
You're now ready to level up. In the next lesson, we'll tackle Redis Streams — the durable, replayable cousin of pub/sub. You'll discover how to handle slow consumers, replay events from the past, and build reliable event pipelines. Understanding pub/sub first gives you a solid foundation for that transition.
Go ahead — subscribe to a channel in your own project, publish a test event, and watch the magic. Then move on to the next lesson.
Practice recap
Open two terminals — one running a Python subscriber on a channel like practice.events, the other a publisher sending JSON messages. Try adding a second subscriber on the same channel and confirm both receive every message. Then experiment with pattern subscriptions and note how messages are delivered. Finally, try publishing a message before any subscriber connects — observe that it's lost, reinforcing the fire-and-forget behavior.
Common mistakes
- Publishing before subscribers connect — messages are fire-and-forget, so anything published to an empty channel vanishes. Always ensure subscribers are live first.
- Using pub/sub for durable job queues — pub/sub doesn't persist messages or track acknowledgment; if a subscriber is offline or crashes, the message is lost forever.
- Subscribing twice on the same connection — calling
ps.subscribe()again on an existing pubsub object adds a new subscription, but you might accidentally keep the old one; manage your lifecycle carefully. - Forgetting
decode_responses=True— treating bytes as strings leads to encoding errors or weird output when processing messages in Python. - Ignoring the
typefield inlisten()— without filtering outsubscribe/unsubscribeconfirmation messages, your code may try to process metadata as if it were actual data.
Variations
- Redis Streams — Instead of pub/sub, use Redis Streams for persistent, replayable messages with consumer groups. Great for event sourcing or reliable task queues.
- Redis Pub/Sub with Pattern Subscriptions — Use
PSUBSCRIBEto match multiple channels at once (e.g.,order.*), reducing the number of subscriptions and simplifying code. - Alternative Brokers — Tools like RabbitMQ or Apache Kafka offer similar fan-out behavior but with durability and more advanced routing; choose when you need stronger guarantees.
Real-world use cases
- Live stock price feeds — broadcast price updates to all connected dashboards and trading widgets in real time.
- Multi-service event fan-out — notify a notification service, an analytics pipeline, and a WebSocket server immediately after a user signup.
- Cache invalidation across clusters — publish
user.updatedevents so every microservice holding a user cache can evict and refresh its data on the fly.
Key takeaways
- Redis pub/sub decouples publishers from subscribers using named channels — neither knows about the other.
- Messages are fire-and-forget and not persisted; a subscriber that's offline misses them permanently.
- Use
SUBSCRIBE/PUBLISHfor simple broadcasting, andPSUBSCRIBEfor pattern-based subscriptions. - In Python,
redis-py'spubsub().listen()returns a stream of dicts; always filter formessagetype. - Pub/sub is ideal for real-time fan-out but not for guaranteed delivery; that's when you reach for Redis Streams or a queue.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.