Redis Beyond Caching: Pub/Sub, Streams, and Rate Limiting
Most developers use Redis only as a cache. Explore its full potential — real-time pub/sub messaging, consumer groups with Redis Streams, and bulletproof rate limiting strategies.
Redis as a Message Broker (Pub/Sub)
Redis Pub/Sub allows services to communicate over named channels without knowing about each other. Publishers push messages; any number of subscribers receive them in real time — perfect for notifications, live dashboards, and chat.
// publisher.js
const redis = require('ioredis');
const pub = new redis();
setInterval(() => {
pub.publish('metrics', JSON.stringify({ cpu: process.cpuUsage() }));
}, 1000);
// subscriber.js
const sub = new redis();
sub.subscribe('metrics');
sub.on('message', (_channel, message) => {
const { cpu } = JSON.parse(message);
console.log('CPU:', cpu);
});Redis Streams
Streams are Redis's append-only log. Unlike Pub/Sub, messages are persisted so late consumers can replay history. Consumer groups let multiple workers share the load without duplicate processing.
// Producer — append event to stream
await redis.xadd('orders', '*', 'orderId', '123', 'item', 'laptop');
// Consumer group setup (once)
await redis.xgroup('CREATE', 'orders', 'processors', '$', 'MKSTREAM');
// Worker — read new entries from the group
const entries = await redis.xreadgroup(
'GROUP', 'processors', 'worker-1',
'COUNT', 10, 'BLOCK', 2000,
'STREAMS', 'orders', '>',
);
// Acknowledge after processing
await redis.xack('orders', 'processors', entryId);Sliding Window Rate Limiting
Use a sorted set to implement a sliding-window rate limiter. Score each request by its timestamp and count entries within the window — atomic, accurate, and no cron cleanup needed.
async function isAllowed(userId, limit = 100, windowMs = 60_000) {
const key = `rl:${userId}`;
const now = Date.now();
const floor = now - windowMs;
const [, , count] = await redis
.multi()
.zremrangebyscore(key, '-inf', floor) // prune old entries
.zadd(key, now, `${now}-${Math.random()}`) // record this request
.zcard(key) // count within window
.expire(key, Math.ceil(windowMs / 1000))
.exec();
return count <= limit;
}“Think of Redis not as a cache bolted onto your stack, but as a real-time data infrastructure layer.”