Skip to main content

Redis Pub/Sub

Redis Pub/Sub implements the publish/subscribe messaging paradigm where publishers send messages to channels, and subscribers receive them in real time. It is a fire-and-forget system with no message persistence.


Core Commandsโ€‹

# SUBSCRIBE โ€” listen to one or more channels
SUBSCRIBE news:breaking news:sports

# PSUBSCRIBE โ€” pattern subscribe (glob patterns)
PSUBSCRIBE news:* # All news channels
PSUBSCRIBE user:*.events # All user event channels

# PUBLISH โ€” send a message to a channel
PUBLISH news:breaking "Redis 8.0 released"
# Returns: number of subscribers who received the message

# UNSUBSCRIBE
UNSUBSCRIBE news:sports # Unsubscribe from specific channel
UNSUBSCRIBE # Unsubscribe from all channels

PUNSUBSCRIBE news:* # Pattern unsubscribe

Subscription Lifecycleโ€‹

Subscriber A: SUBSCRIBE chat:room1 chat:room2
โ†’ waiting for messages...

Publisher: PUBLISH chat:room1 "Hello everyone!"
โ†’ Subscriber A receives:
["message", "chat:room1", "Hello everyone!"]

Pattern sub: PSUBSCRIBE chat:*
PUBLISH chat:room2 "New user joined"
โ†’ Pattern subscriber receives:
["pmessage", "chat:*", "chat:room2", "New user joined"]

Delivery Semanticsโ€‹

PropertyBehavior
PersistenceโŒ Zero โ€” messages not stored
Delivery guaranteeAt-most-once โ€” fire-and-forget
Offline subscribersโŒ Miss all messages while disconnected
History/replayโŒ Impossible โ€” no message log
Message orderingโœ… FIFO within a channel
AcknowledgmentโŒ No ACK mechanism

Critical: If a subscriber disconnects and reconnects, it will miss all messages published during its absence. Pub/Sub is only appropriate when message loss is acceptable.


Connection Modesโ€‹

A subscriber connection enters a blocking subscribe mode โ€” it can only receive messages. It cannot send other commands while subscribed (except SUBSCRIBE, UNSUBSCRIBE, PING, RESET, QUIT).

// Spring Boot Redis Pub/Sub
@Configuration
public class PubSubConfig {

@Bean
public RedisMessageListenerContainer listenerContainer(
RedisConnectionFactory factory,
MessageListenerAdapter adapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener(adapter, new PatternTopic("user:*.events"));
return container;
}

@Bean
public MessageListenerAdapter listenerAdapter(UserEventListener listener) {
return new MessageListenerAdapter(listener, "onMessage");
}
}

@Component
public class UserEventListener {
public void onMessage(String message, String channel) {
log.info("Received on {}: {}", channel, message);
}
}

// Publisher
@Service
public class EventPublisher {
private final RedisTemplate<String, String> redisTemplate;

public void publishUserEvent(Long userId, String event) {
redisTemplate.convertAndSend("user:" + userId + ".events", event);
}
}

Pattern Subscription with @EventListenerโ€‹

A cleaner Spring approach using RedisMessageListenerContainer + Spring events:

@Service
public class DynamicSubscriberService {

@Autowired
private RedisMessageListenerContainer container;

@Autowired
private ApplicationEventPublisher eventPublisher;

public void subscribe(String channel) {
container.addMessageListener(
(message, pattern) -> {
String body = new String(message.getBody());
eventPublisher.publishEvent(new RedisMessageEvent(channel, body));
},
new ChannelTopic(channel)
);
}
}

@Component
public class MessageHandler {

@EventListener
public void handleRedisMessage(RedisMessageEvent event) {
System.out.println("Event on " + event.getChannel() + ": " + event.getBody());
}
}

Real-World Use Casesโ€‹

Use CaseWhy Pub/Sub Fits
Live chat (in-memory only)Users online โ€” message loss on disconnect OK
Real-time notificationsPush to connected clients
Cache invalidation broadcastAll nodes invalidate cache entry simultaneously
Dashboard live updatesEmit metrics to connected dashboard (tolerate drops)
Debug events / logging broadcastDevelopment environment tracing
WebSocket fan-out via RedisHorizontal scaling of WebSocket servers

Cache Invalidation Patternโ€‹

Service A updates product:123
โ†’ PUBLISH cache:invalidate "product:123"

Service B (subscribed to cache:invalidate):
โ†’ evict("product:123") from local in-process cache
โ†’ ensures all nodes' L1 caches are invalidated on write

(Uses Pub/Sub for broadcast โ€” doesn't need persistence)
// Publisher: when a product is updated
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
Product saved = repository.save(product);
// Notify all instances to evict their local cache
redisTemplate.convertAndSend("cache-invalidation", "products:" + product.getId());
return saved;
}

// Subscriber: all app instances listen
@Component
public class CacheInvalidationListener implements MessageListener {

@Autowired
private CacheManager cacheManager;

@Override
public void onMessage(Message message, byte[] pattern) {
String key = new String(message.getBody());
String[] parts = key.split(":", 2);
if (parts.length == 2) {
Cache cache = cacheManager.getCache(parts[0]);
if (cache != null) {
cache.evict(parts[1]);
}
}
}
}

Pub/Sub vs Streamsโ€‹

Pub/SubStreams
PersistenceโŒ Noneโœ… Yes (configurable)
Replay historyโŒโœ… By ID range
Offline client supportโŒ (miss messages)โœ… (reads from last consumed ID)
Consumer groupsโŒ (all get all)โœ… (one-to-one distribution within group)
Message acknowledgmentโŒโœ… (XACK)
Pattern matchingโœ… (PSUBSCRIBE)โŒ (use separate streams)
Throughput (simple fan-out)Higher (no storage overhead)Slightly lower
Use caseReal-time ephemeral fanoutReliable event queues

Production guidance: Unless you specifically need zero-overhead real-time broadcast and can tolerate message loss, prefer Redis Streams for production message passing.


Sharding Pub/Sub in Redis Clusterโ€‹

Standard Pub/Sub in Redis Cluster broadcasts to ALL nodes โ€” every PUBLISH is forwarded to all cluster nodes, creating O(N-nodes) overhead.

Redis 7.0+: Sharded Pub/Sub

SSUBSCRIBE channel # Subscribe to sharded channel
SUNSUBSCRIBE channel # Unsubscribe from sharded channel
SPUBLISH channel msg # Publish to specific shard only

Sharded Pub/Sub routes channels to a specific hash slot โ€” messages only go to the node owning that slot. Dramatically reduces cluster-wide broadcast overhead for high-volume apps.


Production Limitations and Solutionsโ€‹

LimitationProblemSolution
No persistenceMessage loss on disconnectRedis Streams for reliability
No ACKCan't confirm deliveryStreams with XACK
Cluster broadcast overheadO(N) node fanoutRedis 7 Sharded Pub/Sub
Slow subscriber blocksPublisher blocked if subscriber is slow (TCP backpressure)Set client-output-buffer-limit pubsub
Memory pressureSlow subscriber accumulates messages in send bufferLimit buffer: client-output-buffer-limit pubsub 8mb 2mb 60
# Redis config: disconnect slow pub/sub subscribers
client-output-buffer-limit pubsub 8mb 2mb 60
# Hard limit: 8mb (immediate disconnect)
# Soft limit: 2mb for 60 seconds โ†’ then disconnect
# Prevents one slow subscriber from consuming all server memory