Pub/Sub Pattern
While Redis lists can be used to implement simple message queues (using rpush and blpop), they have a key limitation: they support only a single consumer. This is a one-to-one model, not a one-to-many distribution system. To achieve one-to-many message distribution, Redis offers the Publish/Subscribe (Pub/Sub) pattern.
In this asynchronous messaging model, a publisher sends messages to a specific channel, and subscribers receive messages by listening to those channels. It's crucial to note that a subscriber must be subscribed to a channel before a message is published; otherwise, it will miss the message.
Subscribing to Channels
In Redis's Pub/Sub model, channels act as the central point connecting publishers and subscribers, similar to a Queue in RabbitMQ or a Topic in Kafka. A subscriber can listen to one or more channels, and a publisher can send messages to a specific channel. When a message arrives on a channel, all subscribed clients receive it.
To subscribe to one or more channels, a client uses the SUBSCRIBE command. Channels do not need to be created in advance.
subscribe news.sports news.music news.weather
A publisher can send a message to a specific channel using the PUBLISH command.
publish news.sports "Kobe retired."
To unsubscribe from a channel, the UNSUBSCRIBE command is used.
unsubscribe news.sports
Pattern-Based Subscription
Redis also suports pattern-based subscriptions using wildcards. The ? character matches a single character, while * matches zero or more characters.
For example, consider three news channels: news.sports, news.music, and news.weather. Three subscribers might have the following subscriptions:
- Subscriber 1, interested in sports:
psubscribe *sports - Subscriber 2, interested in all news:
psubscribe news.* - Subscriber 3, interested in weather:
psubscribe news.weather
A publisher can then send messages to these channels, and the appropriate subscribers will receive them.
publish news.sports "Kobe"
publish news.music "New album released."
publish news.weather "Sunny day ahead."
For performance and persistence reasons, Redis's Pub/Sub is generally not recommended for implementing a full-featured message queue (MQ) in production. However, it's a core mechanism used by some of Redis's internal features.
Redis Transactions
Redis ensures that individual commands like GET, SET, MGET, and MSET are atomic. However, when a sequence of multiple commands needs to be executed as a single, indivisible unit, Redis transactions are required.
Redis transactions have three main characteristics:
- Commands are executed in the order they were queued.
- The transaction is isolated from commands issued by other clients.
- Nesting transactions is not supported; multiple
MULTIcommands are treated as a single transaction block.
Transaction Usage
Redis transactions involve four commands: MULTI (to begin), EXEC (to execute), DISCARD (to cancel), and WATCH (to monitor keys). Consider a scenario where user A wants to transfer 100 credits to user B. Both start with a balance of 1000.
set user_a 1000
set user_b 1000
multi
decrby user_a 100
incrby user_b 100
exec
get user_a
get user_b
The MULTI command starts the transaction. After this, any subsequent commands are queued. The EXEC command then executes all queued commands atomically. If EXEC is not called, none of the commands in the queue are executed.
What if you want to cancel a transaction midway? The DISCARD command can be used to clear the transaction queue and abandon execution.
multi
decrby user_a 100
discard
get user_a
The WATCH Command
To prevent a key's value from being modified by another client during a transaction, Redis provides the WATCH command. This enables a Compare-and-Swap (CAS) optimistic locking behavior. You can WATCH one or more keys. If any watched key is modified before EXEC is called, the entire transaction is aborted. The UNWATCH command can be used to cancel the watch.
| client1 | client2 |
|---|---|
set balance 1000<br></br>watch balance<br></br>multi<br></br>incrby balance 100 |
|
decrby balance 100 |
|
exec [returns nil]<br></br>get balance |
Transaction Pitfalls
Errors during transaction execution fall into two categories: those occurring before EXEC and those occurring during EXEC.
Errors Before EXEC
If a command in the queue has a syntax error (e.g., wrong number of arguments), the entire transaction is rejected, and no commands are executed.
multi
set key1 "value1"
hset key1 field1 value1
exec
Errors During EXEC
If a command fails during execution due to a runtime error (e.g., using a Hash command on a String key), only that specific command fails. Other commands in the transaction may still succeed, which breaks the atomicity guarantee.
flushall
multi
set k1 1
hset k1 a b
exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
get k1
Here, set k1 1 succeeded, but hset k1 a b failed. This partial execution means Redis transactions cannot guarantee full atomicity in the face of runtime errors.
Why No Rollback?
Redis's official stance on this is:
- Command failures are typically due to syntax errors, which should be caught during development, not in production.
- Avoiding rollback support keeps Redis's internal design simple and fast. Rollback cannot fix programming errors.
Since version 2.6, Redis has supported Lua scripting, which provides a more powerful way to execute sequences of commands atomically.