Redis

Real-Time Messaging with Redis Pub-Sub

June 9, 2026
6 min read

Redis Pub/Sub delivers messages from publishers to subscribers in real time, with zero configuration. It's the simplest way to broadcast events across services, push live updates to connected clients, or trigger cache invalidation across a fleet of servers.

How Pub/Sub Works

Pub/Sub uses a fire-and-forget model: publishers send messages to named channels, and all current subscribers receive them instantly. There's no persistence — if a subscriber is offline, it misses the message. This makes it ideal for real-time signals where delivery guarantees don't matter.

# Terminal 1: Subscribe to a channel
SUBSCRIBE notifications:user:42
# Waiting for messages...

# Terminal 2: Publish a message
PUBLISH notifications:user:42 '{"type":"new_message","from":"alice"}'

# Terminal 1 receives:
# 1) "message"
# 2) "notifications:user:42"
# 3) "{\"type\":\"new_message\",\"from\":\"alice\"}"

Pattern Subscriptions with PSUBSCRIBE

PSUBSCRIBE lets you subscribe to channels matching a glob pattern — useful for subscribing to a whole family of channels at once.

# Subscribe to all notification channels for any user
PSUBSCRIBE notifications:user:*

# Subscribe to all events in a namespace
PSUBSCRIBE events:orders:*

# This subscriber receives messages from:
# - events:orders:created
# - events:orders:shipped
# - events:orders:cancelled

Python Publisher and Subscriber

import redis
import json
import threading

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Publisher
def publish_event(channel, event_type, data):
    message = json.dumps({"type": event_type, "data": data})
    recipients = r.publish(channel, message)
    print(f"Delivered to {recipients} subscriber(s)")

# Subscriber (runs in a dedicated thread/process)
def start_subscriber(channels):
    pubsub = r.pubsub()
    pubsub.subscribe(*channels)

    for message in pubsub.listen():
        if message['type'] == 'message':
            event = json.loads(message['data'])
            handle_event(message['channel'], event)

def handle_event(channel, event):
    print(f"Channel: {channel}, Type: {event['type']}")
    # Handle the event...

# Start subscriber in background thread
thread = threading.Thread(
    target=start_subscriber,
    args=(["notifications:global", "cache:invalidate"],),
    daemon=True
)
thread.start()

# Publish from anywhere
publish_event("cache:invalidate", "product_updated", {"id": 42})

Cache Invalidation Across Servers

One of the most practical Pub/Sub use cases: when a product is updated, publish an invalidation event so every app server flushes its local cache entry.

def update_product(product_id, data):
    db.update("products", product_id, data)
    # Broadcast invalidation to all servers
    r.publish("cache:invalidate", json.dumps({
        "key": f"product:{product_id}"
    }))

Limitations of Pub/Sub

  • No persistence — offline subscribers miss messages entirely
  • No acknowledgment — no way to know if a subscriber processed the message
  • No replay — you can't re-read past messages
  • No load balancing — every subscriber receives every message (fan-out only)

When to Use Redis Streams Instead

If your use case needs any of the following, use Redis Streams instead of Pub/Sub:

  • Message persistence (survives subscriber restarts)
  • Multiple workers sharing load (consumer groups)
  • Message acknowledgment and retry
  • Replay from a specific point in time

Key Takeaways

  • PUBLISH channel message broadcasts to all current subscribers instantly
  • SUBSCRIBE listens on specific channels; PSUBSCRIBE uses glob patterns
  • Pub/Sub is fire-and-forget — perfect for cache invalidation, live dashboards, and real-time alerts
  • Use Redis Streams when you need durability, acknowledgment, or load-balanced consumption