Our overview article on Redis pub/sub discusses the purpose of pub/sub and describes the design choices of Redis pub/sub in particular. We’ll now turn to how to use Redis pub/sub by stepping through each of the main concepts of Redis pub/sub: channels, publishing, subscribing, and pattern-matching, using the noderedis node.js client.
A channel is a name used to categorize messages published on the pub/sub
system. Channels can have system-dependent names, like system-health:i-36a44b83
,
trade-prices:RAX
, temp-reading:living-room
, or something very generic, like
events
. Any subscriber interested in the data that appears on a channel can
listen to it, and new publishers and subscribers are easily added as the system
grows.
To find out which channels are active on a Redis server, you can use the PUBSUB
CHANNELS
command, which returns nothing when a system is not yet being used
for pub/sub:
$ redis-cli pubsub channels
(empty list or set)
Channels exist on the system only when a subscriber is listening to messages on
it, so there is no need to ever “create” or “remove” channels – they exist only
while a subscriber is listening. To see this, we can use redis-cli
to act as a
subscriber in one console window, and run PUBSUB CHANNELS
again in another
window:
Console 1:
$ redis-cli subscribe events
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "events"
3) (integer) 1
Console 2:
$ redis-cli pubsub channels
1) "events"
As soon as Console 1 disconnects the system will again have no channels.
The PUBLISH
command is used to publish messages.
PUBLISH channel message
All subscribers listening on the channel will be sent the message. If no subscribers are around to receive the message the message is dropped.
After creating a regular connection to Redis, publish can be used like any other command:
var client = require("redis").createClient();
client.publish("temp-reading:living-room", "37.0");
Publishing data to a channel is a fast operation, so it’s commonly used in tandem with other operations. Combining operations like this unlocks the power of Redis:
var client = require("redis").createClient();
client.multi()
.publish("temp-reading:living-room", "37.0")
.lpush("recent-temperatures", "37.0")
.ltrim("recent-temperatures", 0, 99)
.exec();
This is the same operation as the earlier example for the pub/sub system, but in
the same MULTI/EXEC
transaction the temperature is also
pushed onto a list which keeps the 100 most recent temperatures.
This short example shows a way to integrate Redis pub/sub into a larger system design that includes message history. An operation like the one above lets other clients query recent values as well as subscribe to new ones.
The SUBSCRIBE
command is used to subscribe to channels. This command puts the
client in a special “subscribed” state where it no longer sends commands other
than additional SUBSCRIBE
or UNSUBSCRIBE
commands.
SUBSCRIBE channel [channel ...]
This subscriber could be used with the earlier publisher example:
var subscriber = require("redis").createClient();
subscriber.on("message", function(channel, message) {
console.log("A temperature of " + message + " was read.");
});
subscriber.subscribe("temp-reading:living-room");
This uses a simple event emitter which calls a function every time a message arrives. The handling function just logs the temperature to the console right now – it could do anything.
As mentioned earlier, subscribed clients are in a special mode and cannot be used for other commands. To use other commands, you’ll create a separate client connection to Redis:
var redis = require("redis")
, subscriber = redis.createClient()
, client = redis.createClient();
subscriber.on("message", function(channel, message) {
console.log("A temperature of " + message + " was read.");
client.incr("temp-count");
});
subscriber.subscribe("temp-reading:living-room");
This handler logs the message to the console and increments a Redis counter. In some cases you might calculate a result and then publish that result to a separate channel for other subscribers to read – just don’t publish it back to the same channel you’re listening on, as you’ll cause an infinite loop :)
The PSUBSCRIBE
command is used for matching channel patterns.
PSUBSCRIBE pattern [pattern ...]
This works just the same as a normal SUBSCRIBE
command but allows you to match
channels with names that match a pattern. This lets publishers be very specific
about the information they’re publishing, and allows subscribers to listen to
many channels without knowing their precise names.
The supported patterns are simple: *
matches any characters, ?
matches
a single character, and brackets can be used to match a set of acceptable
characters, like [acd]
.
Match temperature readings from several rooms:
PSUBSCRIBE temp-reading:*
Match events from A/B tests like site-link:logo:a:clickrate
and
site-link:logo:b:clickrate
:
PSUBSCRIBE site-link:logo:?:clickrate
Match events across a series of AWS instances, for published events like
system-health:us-east-1a:i-36a44b83
and system-health:us-east-1c:i-73657420636f636f6
:
PSUBSCRIBE system-health:us-east-1[acd]:i-*
In this example, which could also be used with the earlier publishing example, the temperature is logged along with the room where the temperature was read.
var subscriber = require("redis").createClient();
subscriber.on("pmessage", function(pattern, channel, message) {
var room = channel.split(":")[1];
console.log("A temperature of " + message + " was read in " + room);
});
subscriber.psubscribe("temp-reading:*");
The pattern-based subscriber gets a few more details in its callback: not only the channel and message, but the particular pattern that was matched - since subscribers can listen on multiple channels or patterns.
PUBLISH
Every command in Redis has a documented time complexity, and most of the time
these complexities are intuitive. Since the PUBLISH
operation feels very
simple (almost like SET
) one might assume that its complexity is O(1). In fact
the time complexity of PUBLISH
grows linearly according to the behavior of
subscribers.
When the PUBLISH
command runs it must step through all of the patterns
subscribed to that might match the channel and all of the subscribers who
should receive the message, resulting in a time complexity of O(N+M).
Most deployments will never experience any performance trouble with the
complexity of PUBLISH
, but tracking the performance of that command over time
is still wise – be careful if your code automatically generates patterns of
channels to listen on.
We’d love to feature a walk-through of some great examples of apps built using Redis pub/sub. If you know of some good examples, send us a note at support@memetria.com – we’ll do our best to feature some of the code we see.