Redis发布订阅模式实现原理
前言发布订阅系统在我们日常的工作中经常会使用到,这种场景大部分情况我们都是使用消息队列,常用的消息队列有 Kafka,RocketMQ,RabbitMQ,每一种消息队列都有其特性,很多时候我们可能不需要独立部署相应的消息队列,只是简单的使用,而且数据量也不会太大,这种情况下,我们就可以使用 Redis 的 Pub/Sub 模型。一、Redis发布订阅Redis 的发布订阅功能主要由 PUBLISH
前言
发布订阅系统在我们日常的工作中经常会使用到,这种场景大部分情况我们都是使用消息队列,常用的消息队列有 Kafka,RocketMQ,RabbitMQ,每一种消息队列都有其特性,很多时候我们可能不需要独立部署相应的消息队列,只是简单的使用,而且数据量也不会太大,这种情况下,我们就可以使用 Redis 的 Pub/Sub 模型。
一、Redis发布订阅
Redis 的发布订阅功能主要由 PUBLISH,SUBSCRIBE,PSUBSCRIBE 命令组成,一个或者多个客户端订阅某个或者多个频道,当其他客户端向该频道发送消息的时候,订阅了该频道的客户端都会收到对应的消息。
二、发布订阅模式的基本命令
Psubscribe 命令订阅一个或多个符合给定模式的频道。每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets )
Psubscribe 命令基本语法如下:
PSUBSCRIBE pattern [pattern ...]
Redis Pubsub 命令用于查看订阅与发布系统状态,它由数个不同格式的子命令组成。
PUBSUB <subcommand> [argument [argument ...]]
Publish 命令用于将信息发送到指定的频道。
Publish 命令基本语法如下:
PUBLISH channel message
Punsubscribe 命令用于退订所有给定模式的频道。
Punsubscribe 命令基本语法如下:
PUNSUBSCRIBE [pattern [pattern ...]]
Subscribe 命令用于订阅给定的一个或多个频道的信息。
Subscribe 命令基本语法如下:
SUBSCRIBE channel [channel ...]
Unsubscribe 命令用于退订给定的一个或多个频道的信息。
Unsubscribe 命令基本语法如下:
UNSUBSCRIBE channel [channel ...]
三、发布订阅模式的实现原理
Redis通过PUBLISH、SUBSCRIBE和PSUBSCRIBE等命令实现了发布和订阅功能。
- 通过SUBSCRIBE命令订阅某个频道后,redis-server里面维护了一个字典,字典的键就是一个个channel,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到指定channel的订阅链表中。
- 通过PUBLISH命令向订阅者发送信息,redis-server会使用给定的频道作为键,在他所维护的channel字典中查找记录订阅该频道的所有客户端的链表,通过遍历这个链表,来将信息发布给所有的订阅者
Pub/Sub 底层存储结构
订阅 Channel
在 Redis 的底层结构中,客户端和频道的订阅关系是通过一个字典加链表的结构保存的,形式如下:
在 Redis 的底层结构中,Redis 服务器结构体中定义了一个 pubsub_channels
字典
struct redisServer {
//用于保存所有频道的订阅关系
dict *pubsub_channels
}
在这个字典中,key 代表的是频道名称,value 是一个链表,这个链表里面存放的是所有订阅这个频道的客户端。
所以当有客户端执行订阅频道的动作的时候,服务器就会将客户端与被订阅的频道在 pubsub_channels 字典中进行关联。
这个时候有两种情况:
-
该渠道是首次被订阅:首次被订阅说明在字典中并不存在该渠道的信息,那么程序首先要创建一个对应的 key,并且要赋值一个空链表,然后将对应的客户端加入到链表中。此时链表只有一个元素。
-
该渠道已经被其他客户端订阅过:这个时候就直接将对应的客户端信息添加到链表的末尾就好了。
比如,如果有一个新的客户端 Client 08 要订阅 run
渠道,那么上图就会变成
如果 Client 08 要订阅一个新的渠道 new_sport
,那么就会变成
取消订阅
上面介绍的是单个 Channel 的订阅,相反的如果一个客户端要取消订阅相关 Channel,则无非是找到对应的 Channel 的链表,从中删除对应的客户端,如果该客户端已经是最后一个了,则将对应 Channel 也删除。
模式订阅结构
模式渠道的订阅与单个渠道的订阅类似,不过服务器是将所有模式的订阅关系都保存在服务器状态的pubsub_patterns
属性里面。
struct redisServer{
//保存所有模式订阅关系
list *pubsub_patterns;
}
与订阅单个 Channel 不同的是,pubsub_patterns 属性是一个链表,不是字典。节点的结构如下:
struct pubsubPattern{
//订阅模式的客户端
redisClient *client;
//被订阅的模式
robj *pattern;
} pubsubPattern;
其实 client
属性是用来存放对应客户端信息,pattern
是用来存放客户端对应的匹配模式。
所以对应上面的 Client-06 模式匹配的结构存储如下
在pubsub_patterns
链表中有一个节点,对应的客户端是 Client-06,对应的匹配模式是run*
。
订阅模式
当某个客户端通过命令psubscribe
订阅对应模式的 Channel 时候,服务器会创建一个节点,并将 Client 属性设置为对应的客户端,pattern 属性设置成对应的模式规则,然后添加到链表尾部。
-
创建新节点;
-
给节点的属性赋值;
-
将节点添加到链表的尾部;
退订模式
退订模式的命令是punsubscribe
,客户端使用这个命令来退订一个或者多个模式 Channel。服务器接收到该命令后,会遍历pubsub_patterns
链表,将匹配到的 client 和 pattern 属性的节点给删掉。这里需要判断 client 属性和 pattern 属性都合法的时候再进行删除。
遍历所有的节点,当匹配到相同 client 属性和 pattern 属性的时候就进行节点删除。
发布消息
当一个客户端执行了publish channelName message
命令的时候,服务器会从pubsub_channels
和pubsub_patterns
两个结构中找到符合channelName
的所有 Channel,进行消息的发送。在 pubsub_channels
中只要找到对应的 Channel 的 key 然后向对应的 value 链表中的客户端发送消息。
更多推荐
所有评论(0)