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