Redis入门11-发布与订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息

带着问题看世界:

  1. 它的基本用法是什么
  2. 它的基本原理是什么
  3. 它跟其他消息中间件有什么区别,为什么很少听到用它
  4. 它的使用场景是哪些

基本功能

Redis有两种发布/订阅模式:

  • 基于频道(Channel)的发布/订阅
  • 基于模式(pattern)的发布/订阅

基于频道的发布/订阅

消息订阅者通过 subscribe channel [channe ...] 订阅频道

执行命令客户端会进入订阅状态,处于此状态下客户端不能使用除subscribeunsubscribepsubscribepunsubscribe这四个属于"发布/订阅"之外的命令,否则会报错。

进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消类型的不同,第二个和第三个参数的含义可能不同。消息类型的取值可能是以下3个:

  • subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。

    1
    2
    3
    4
    5
    127.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
    4
    127.0.0.1:6379> unsubscribe channel:1
    1) "unsubscribe" #表示成功取消订阅
    2) "channel:1" #表示取消订阅频道
    3) (integer) 0 #表示当前客户端订阅的频道数量

​ 实际执行 subscribe 后 执行 Ctrl-C 就退出去,并不需要执行 unsubscribe,我想这个在实际代码执行的时候才用的到

当没有人订阅时候,消息发送失败

1
2
127.0.0.1:6379> publish channel:1 hello
(integer) 0

基于模式的发布/订阅

如果有某个/某些模式和这个频道匹配的话,那么所有订阅这个/这些频道的客户端也同样会收到信息

redis-subscribe

**通配符中?表示1个占位符,表示任意个占位符(包括0),?表示1个以上占位符

消息订阅

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> psubscribe c? b* d?*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "c?"
3) (integer) 1
1) "psubscribe"
2) "b*"
3) (integer) 2
1) "psubscribe"
2) "d?*"
3) (integer) 3

消息发送结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> publish c m1
(integer) 0
127.0.0.1:6379> publish c1 m1
(integer) 1
127.0.0.1:6379> publish c11 m1
(integer) 0
127.0.0.1:6379> publish b m1
(integer) 1
127.0.0.1:6379> publish b1 m1
(integer) 1
127.0.0.1:6379> publish b11 m1
(integer) 1
127.0.0.1:6379> publish d m1
(integer) 0
127.0.0.1:6379> publish d1 m1
(integer) 1
127.0.0.1:6379> publish d11 m1
(integer) 1

注意:

  • 使用psubscribe命令可重复订阅同一个频道,如客户端执行了psubscribe c? c?*。这时向c1发布消息客户端会接受到两条消息,而同时publish命令的返回值是2而不是1。同样的,如果有另一个客户端执行了subscribe c1psubscribe c?*的话,向c1发送一条消息该客户顿也会受到两条消息(但是是两种类型:message和pmessage),同时publish命令也返回2.
  • punsubscribe命令可以退订指定的规则,用法是: punsubscribe [pattern [pattern ...]],如果没有参数则会退订所有规则。
  • 使用punsubscribe只能退订通过psubscribe命令订阅的规则,不会影响直接通过subscribe命令订阅的频道;同样unsubscribe命令也不会影响通过psubscribe命令订阅的规则。
  • punsubscribe命令退订某个规则时不会将其中的通配符展开,而是进行严格的字符串匹配,所以punsubscribe * 无法退订c*规则,而是必须使用punsubscribe c*才可以退订。(它们是相互独立的)

实现原理

频道的是通过(pubsub_channels)字典实现的,这个字典就用于保存订阅频道的信息:字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端

pubsub_struct

订阅:当执行 SUBSCRIBE 命令,首先判断频道是否存在,没有则新建。然后将客户端添加到频道的对应值的链表中,如果订阅多个频道就添加到多个频道中

发布:当执行 PUBLISH 命令,首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端

退订:使用 UNSUBSCRIBE 命令退订指定的频道,从 pubsub_channels 字典的给定频道中,删除关于当前客户端的信息

模式的底层原理是链表

每当执行 PSUBSCRIBE 的时候,程序就创建一个包含客户端信息和被订阅模式的 pubsubPattern 结构, 并将该结构添加到 pubsub_patterns 链表中,相同的模式下存储多个客户端

redis-psubscribe

订阅:如果一个执行 PSUBSCRIBE 就会在链表中建立一个节点(注意相同的模式也是不同的节点),通过遍历整个链表可以检查所有被订阅的模式

发布:当执行 PUBLISH 命令,遍历链表定位到模式对应的客户端

退订:使用 PUNSUBSCRIBE 命令可以退订指定的模式

所以退订规则的时候是严格的字符串匹配,不会因为 退订 tweet.* 而将 tweet.shop.* 退订掉

问题1:它跟其他中间件有什么区别

  • 订阅的消费者需要一直执行,阻塞获取消息,如果断开会表示退订。当出现网络波动或者一个消费者挂掉之后,消息就会直接被丢弃掉,Redis本身不会存储消息
  • 不能重复消费

问题2:它的应用场景是什么

  • 如果不在意消息的丢失,需要一个轻量级的消息通信方式,Redis发布与订阅模式是一个很好的选择

参考链接

  1. https://pdai.tech/md/db/nosql-redis/db-redis-x-pub-sub.html