Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
带着问题看世界:
- 它的基本用法是什么
- 它的基本原理是什么
- 它跟其他消息中间件有什么区别,为什么很少听到用它
- 它的使用场景是哪些
基本功能
Redis有两种发布/订阅模式:
- 基于频道(Channel)的发布/订阅
- 基于模式(pattern)的发布/订阅
基于频道的发布/订阅
消息订阅者通过 subscribe channel [channe ...]
订阅频道
执行命令客户端会进入订阅状态,处于此状态下客户端不能使用除subscribe
、unsubscribe
、psubscribe
和punsubscribe
这四个属于"发布/订阅"之外的命令,否则会报错。
进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消类型的不同,第二个和第三个参数的含义可能不同。消息类型的取值可能是以下3个:
-
subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。
1
2
3
4
5127.0.0.1:6379> subscribe channel:1
Reading messages... (press Ctrl-C to quit)
1) "subscribe" # 消息类型
2) "channel:1" # 消息隧道
3) (integer) 1 # 当前客户端订阅的频道数量 -
message。表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。
1
2
3
4
5
6
7
8发送端
127.0.0.1:6379> publish channel:1 hello
(integer) 1
接收端
1) "message" #消息类型表示接受到的消息
2) "channel:1" #表示产生消息的频道
3) "hello" #表示消息的内容 -
unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非"发布/订阅"模式的命令了。
1
2
3
4127.0.0.1:6379> unsubscribe channel:1
1) "unsubscribe" #表示成功取消订阅
2) "channel:1" #表示取消订阅频道
3) (integer) 0 #表示当前客户端订阅的频道数量
实际执行 subscribe
后 执行 Ctrl-C
就退出去,并不需要执行 unsubscribe
,我想这个在实际代码执行的时候才用的到
当没有人订阅时候,消息发送失败
1 | 127.0.0.1:6379> publish channel:1 hello |
基于模式的发布/订阅
如果有某个/某些模式和这个频道匹配的话,那么所有订阅这个/这些频道的客户端也同样会收到信息

**通配符中?表示1个占位符,表示任意个占位符(包括0),?表示1个以上占位符
消息订阅
1 | 127.0.0.1:6379> psubscribe c? b* d?* |
消息发送结果
1 | 127.0.0.1:6379> publish c m1 |
注意:
- 使用
psubscribe
命令可重复订阅同一个频道,如客户端执行了psubscribe c? c?*
。这时向c1
发布消息客户端会接受到两条消息,而同时publish命令的返回值是2而不是1。同样的,如果有另一个客户端执行了subscribe c1
和psubscribe c?*
的话,向c1发送一条消息该客户顿也会受到两条消息(但是是两种类型:message和pmessage),同时publish命令也返回2. punsubscribe
命令可以退订指定的规则,用法是:punsubscribe [pattern [pattern ...]]
,如果没有参数则会退订所有规则。- 使用
punsubscribe
只能退订通过psubscribe命令订阅的规则,不会影响直接通过subscribe命令订阅的频道;同样unsubscribe命令也不会影响通过psubscribe命令订阅的规则。 punsubscribe
命令退订某个规则时不会将其中的通配符展开,而是进行严格的字符串匹配,所以punsubscribe *
无法退订c*
规则,而是必须使用punsubscribe c*
才可以退订。(它们是相互独立的)
实现原理
频道的是通过(pubsub_channels)字典实现的,这个字典就用于保存订阅频道的信息:字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端

订阅:当执行 SUBSCRIBE
命令,首先判断频道是否存在,没有则新建。然后将客户端添加到频道的对应值的链表中,如果订阅多个频道就添加到多个频道中
发布:当执行 PUBLISH
命令,首先根据 channel
定位到字典的键, 然后将信息发送给字典值链表中的所有客户端
退订:使用 UNSUBSCRIBE
命令退订指定的频道,从 pubsub_channels
字典的给定频道中,删除关于当前客户端的信息
模式的底层原理是链表
每当执行 PSUBSCRIBE
的时候,程序就创建一个包含客户端信息和被订阅模式的 pubsubPattern
结构, 并将该结构添加到 pubsub_patterns
链表中,相同的模式下存储多个客户端

订阅:如果一个执行 PSUBSCRIBE
就会在链表中建立一个节点(注意相同的模式也是不同的节点),通过遍历整个链表可以检查所有被订阅的模式
发布:当执行 PUBLISH
命令,遍历链表定位到模式对应的客户端
退订:使用 PUNSUBSCRIBE
命令可以退订指定的模式
所以退订规则的时候是严格的字符串匹配,不会因为 退订 tweet.*
而将 tweet.shop.*
退订掉
问题1:它跟其他中间件有什么区别
- 订阅的消费者需要一直执行,阻塞获取消息,如果断开会表示退订。当出现网络波动或者一个消费者挂掉之后,消息就会直接被丢弃掉,Redis本身不会存储消息
- 不能重复消费
问题2:它的应用场景是什么
- 如果不在意消息的丢失,需要一个轻量级的消息通信方式,Redis发布与订阅模式是一个很好的选择