Redis 发布/订阅
Redis 发布/订阅
Redis 的发布/订阅功能主要由 PUBLISH,SUBSCRIBE,PSUBSCRIBE 命令组成。
一个或者多个客户端订阅了某个或者多个频道,当其他客户端向频道发送消息,订阅了该频道的客户端会收到对应的消息。
这个功能提供两种信息机制,分别为 订阅/发布到频道和订阅/发布到模式。
频道的订阅和信息发送
Redis 的 subscribe 命令可以订阅一个或多个频道,当有消息发送到被订阅的频道时,消息会发送给所有订阅频道的客户端。
如下图,client1,client2,client3订阅了频道 channel 。

当有消息通过publish 命令发送给频道channel时,这个消息会发送给订阅该频道的客户端。

下面说一下 subscribe 和 publish 的命令的实现,频道发布订阅原理。
订阅频道
每个 Redis 服务端进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典保存订阅频道的信息
struct redisServer {
// ...
dict *pubsub_channels;
// ...
};
其中字典的键为被订阅的频道,值为所有订阅该频道的客户端,是一个链表的结构。
如下图,客户端client1、client2、client3订阅了频道channel1,客户端client4、client5、client6订阅了频道channel2。

当一个客户端使用 subscribe命令订阅频道时,程序会把客户端加在字典中频道对应的链表。
如图,客户端client7 执行命令 subscribe channel2 ,上图则变成下面的图。

subscribe 命令伪代码表示如下:
def SUBSCRIBE(client, channels):
# 遍历所有输入频道
for channel in channels:
# 将客户端添加到链表的末尾
redisServer.pubsub_channels[channel].append(client)
发送消息到频道
调用 publish channel message 命令, 程序首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。
如图,客户端执行命令 publish channel1 "hello world!" ,那么client1、client2、client3 三个客户端都将接收到 "hello world!" 信息:

publish 命令伪代码表示如下:
def PUBLISH(channel, message):
# 遍历所有订阅频道 channel 的客户端
for client in server.pubsub_channels[channel]:
# 将信息发送给它们
send_message(client, message)
退订频道
使用 unsubscribe 命令可以退订指定的频道, 这个命令执行的是订阅的反操作: 它从 pubsub_channels 字典的给定频道(键)中, 删除关于当前客户端的信息, 这样被退订频道的信息就不会再发送给这个客户端。
模式的订阅和信息发送
使用publish 命令发送消息给频道时,不仅订阅该频道的客户端会接受到消息,如果某些模式和这个频道匹配,那么所有订阅该模式的客户端也将接受到消息。
下图表示一个频道和一个模式,模式channel*匹配频道channel。

当有信息发送到 channel 频道时, 信息除了发送给 client1、client2、client3之外, 还会发送给订阅 channel*模式的client4和client5:

订阅模式
redisServer.pubsub_patterns 属性是一个链表,链表中保存着所有和模式相关的信息:
struct redisServer {
// ...
list *pubsub_patterns;
// ...
};
链表中的每个节点都包含一个 redis.h/pubsubPattern 结构:
typedef struct pubsubPattern {
redisClient *client;
robj *pattern;
} pubsubPattern;
client 属性保存着订阅模式的客户端,而 pattern 属性则保存着被订阅的模式。
当调用 psubscribe 命令订阅一个模式时, 程序就创建一个包含客户端信息和被订阅模式的 pubsubPattern 结构, 并将该结构添加到 redisServer.pubsub_patterns 链表中。
如下图,展示了一个包含两个模式的 pubsub_patterns 链表,其中client1和client2都订阅着channle*模式。

这时客户端 client3 执行命令 psubscribe channel_test*, 那么 pubsub_patterns 链表将被更新成这样:

通过遍历整个 pubsub_patterns 链表,程序可以检查所有正在被订阅的模式,以及订阅这些模式的客户端。
发送信息到模式
发送信息到模模式也是 publish 命令,上面提到的 发送信息到频道 只给出了部分代码。实际上 publish 命令将 message 发送到所有订阅 channel 的客户端之外, 它还会将 channel 和 pubsub_patterns 中的模式进行对比, 如果 channel 和某个模式匹配的话, 那么也将 message 发送到订阅那个模式的客户端。
完整的 publish 伪代码
def PUBLISH(channel, message):
# 遍历所有订阅频道 channel 的客户端
for client in server.pubsub_channels[channel]:
# 将信息发送给它们
send_message(client, message)
# 取出所有模式,以及订阅模式的客户端
for pattern, client in server.pubsub_patterns:
# 如果 channel 和模式匹配
if match(channel, pattern):
# 那么也将信息发给订阅这个模式的客户端
send_message(client, message)
举个例子,如果 Redis 服务器的 pubsub_patterns 状态如下:

那么当消息发送到 channel 的时候,除了订阅channel的客户端会接受到消息,上图的client1和client2也会接受到消息。因为这两个客户端订阅的模式匹配了频道channel。
退订模式
使用 punsubscribe 命令可以退订指定的模式, 这个命令执行的是订阅模式的反操作: 程序会删除 redisServer.pubsub_patterns 链表中, 所有和被退订模式相关联的 pubsubPattern 结构, 这样客户端就不会再收到和模式相匹配的频道发来的信息。
应用场景
1 构建实时消息系统,比如普通的即时聊天,群聊等功能
2 在我们的分布式架构中,常常会遇到读写分离的场景,在写入的过程中,就可以使用redis发布订阅,使得写入值及时发布到各个读的程序中,就保证数据的完整一致性
3 在一个博客网站中,有100个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们
参考文献
Redis 设计与实现
https://www.cnblogs.com/liuqingzheng/p/10009541.html
https://www.cnblogs.com/yitudake/p/6747995.html

浙公网安备 33010602011771号