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โ
| Property | Behavior |
|---|---|
| Persistence | โ Zero โ messages not stored |
| Delivery guarantee | At-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 Case | Why Pub/Sub Fits |
|---|---|
| Live chat (in-memory only) | Users online โ message loss on disconnect OK |
| Real-time notifications | Push to connected clients |
| Cache invalidation broadcast | All nodes invalidate cache entry simultaneously |
| Dashboard live updates | Emit metrics to connected dashboard (tolerate drops) |
| Debug events / logging broadcast | Development environment tracing |
| WebSocket fan-out via Redis | Horizontal 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/Sub | Streams | |
|---|---|---|
| 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 case | Real-time ephemeral fanout | Reliable 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โ
| Limitation | Problem | Solution |
|---|---|---|
| No persistence | Message loss on disconnect | Redis Streams for reliability |
| No ACK | Can't confirm delivery | Streams with XACK |
| Cluster broadcast overhead | O(N) node fanout | Redis 7 Sharded Pub/Sub |
| Slow subscriber blocks | Publisher blocked if subscriber is slow (TCP backpressure) | Set client-output-buffer-limit pubsub |
| Memory pressure | Slow subscriber accumulates messages in send buffer | Limit 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