这篇文章,我们聊聊 Redis 的高级特性之一: 发布订阅。
1 发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
图中,消费者1和消费者2 订阅了 Redis 服务的频道 channel ,当生产者通过 PUBLISH 命令发送给频道 channel 时, 这个消息就会被发送给订阅它的两个客户端。
下表列出了 Redis 发布订阅常用命令:
命令 | 用法 | 描述 |
---|---|---|
PSUBSCRIBE | PSUBSCRIBE pattern [pattern ...] | 订阅一个或多个符合给定模式的频道 |
PUBSUB | PUBSUB subcommand [argument [argument ...]] | 查看订阅与发布系统状态 |
PUBLISH | PUBLISH channel message | 将信息发送到指定的频道 |
PUNSUBSCRIBE | PUNSUBSCRIBE [pattern [pattern ...]] | 退订所有给定模式的频道 |
SUBSCRIBE | SUBSCRIBE channel [channel ...] | 订阅给定的一个或多个频道的信息 |
UNSUBSCRIBE | UNSUBSCRIBE [channel [channel ...]] | 指退订给定的频道 |
2 客户端演示
我们开启两个 redis-cli 客户端,演示了发布订阅是如何工作的。
客户端 A 执行订阅命令:
subscribe mychannel
订阅命令执行成功后:
客户端 B 执行发布命令:
publish mychannel helloworld
发布命令执行成功后,客户端 A 就会接收到新的消息:
3 实现原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer
结构, 结构的 pubsub_channels
属性是一个字典, 这个字典就用于保存订阅频道的信息:
struct redisServer {
// ...
dict *pubsub_channels;
// ...
};
其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
下图中, client2 、 client5 和 client1 就订阅了 channel1
, 而其他频道也分别被别的客户端所订阅:
当客户端调用 SUBSCRIBE 命令时, 程序就将客户端和要订阅的频道在 pubsub_channels 字典中关联起来。
举个例子,如果客户端 client10086 执行命令 SUBSCRIBE channel1 channel2 channel3
,那么前面展示的 pubsub_channels 将变成下面这个样子:
当调用 PUBLISH channel message
命令, 程序首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。
如果某个客户端执行命令 PUBLISH channel1 "hello moto"
,那么 client2 、 client5 和 client1 三个客户端都将接收到 "hello moto" 信息:
4 适用场景
在使用发布订阅功能时,需要理解两点。
1、保证先让消费者先订阅队列,然后再让生产者发布消息。
如果消费者异常挂掉并重新上线,它只能接收新的消息。在其下线期间,生产者发布的消息将无法被消费者接收,因为找不到该消费者,这些消息会被丢弃。
如果所有消费者都下线,那么生产者发布的消息将因为没有任何消费者而全部被丢弃。
2、发布订阅不具备数据持久化的能力
发布订阅相关的操作不会被写入到 Redis 数据库(RDB)或追加写入文件(AOF)中。如果 Redis 宕机并重新启动,Pub/Sub 的数据将会全部丢失。
基于上面的分析,发布订阅功能特别适合对少量消息丢失不敏感的通知类型的场景。
5 实战演示
笔者开源了一个短信服务,为了提升系统性能,应用信息存储在本地内存中,定时任务会每隔30秒重新刷新一次缓存。
但在分布式部署的场景下,不同应用间的本地内存数据可能因为执行时间差异导致数据不同步。
基于这样的场景,我们可以使用 Redis 的发布订阅功能,实现本地内存的及时刷新。
1、配置 Redis 频道监听器容器
2、触发发布消息
当应用发送变化时,比如新增、修改、删除,则调用发送消息到频道方法。
下图,当我们启动两个短信平台应用,在控制台修改应用信息时,我们发现两个应用的本地内存都发生变化了。
6 总结
- Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
消费者1和消费者2 订阅了 Redis 服务的频道 channel ,当生产者通过 PUBLISH 命令发送给频道 channel 时, 这个消息就会被发送给订阅它的两个客户端。
在使用发布订阅功能时,需要理解两点。
保证先让消费者先订阅队列,然后再让生产者发布消息。
发布订阅不具备数据持久化的能力。
发布订阅功能特别适合对少量消息丢失不敏感的通知类型的场景。
笔者开源的短信平台项目,使用了 Redis 的发布订阅功能实现本地内存的实时同步。
短信平台地址:https://github.com/makemyownlife/platform-sms
参考资料:
https://redisbook.readthedocs.io/en/latest/feature/pubsub.html