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

Sponsored

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.

  1. A subscriber connects to Redis and issues SUBSCRIBE channel. From that moment, Redis keeps a connection open for that client dedicated to receiving messages.
  2. A publisher connects (any Redis client) and issues PUBLISH channel payload. The payload is a string — typically JSON or plain text.
  3. Redis looks up all subscribers for that channel and pushes the message to each one, in the order they subscribed.
  4. 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 like redis-py.
  5. The subscriber handles the message — e.g., updates a cache, sends an email, or triggers a WebSocket broadcast.
  6. 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=True in redis-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 connectivityredis-cli ping should return PONG.
  • Is the subscriber actually subscribed? Look for the subscribe confirmation in the loop output. If you only see subscribe messages, your filter on message['type'] is correct.
  • Are you publishing to the same channel name? Channel names are case-sensitive. Order.Created is not order.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 timeout config; 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-py client 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 type field in listen() — without filtering out subscribe/unsubscribe confirmation messages, your code may try to process metadata as if it were actual data.

Variations

  1. Redis Streams — Instead of pub/sub, use Redis Streams for persistent, replayable messages with consumer groups. Great for event sourcing or reliable task queues.
  2. Redis Pub/Sub with Pattern Subscriptions — Use PSUBSCRIBE to match multiple channels at once (e.g., order.*), reducing the number of subscriptions and simplifying code.
  3. 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.updated events 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/PUBLISH for simple broadcasting, and PSUBSCRIBE for pattern-based subscriptions.
  • In Python, redis-py's pubsub().listen() returns a stream of dicts; always filter for message type.
  • 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.