[TOC]
1. 介绍
在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
https://zh.wikipedia.org/wiki/%E5%8F%91%E5%B8%83/%E8%AE%A2%E9%98%85
发布/订阅的拓扑:
2. redis 实现
在redis文档你可以找到发布/订阅
发布订阅只有6条命令。
3. 命令
发布/订阅的吗,命令很少,你可以在redis发布/订阅命令查看
4. 订阅
命令:==SUBSCRIBE channel [channel1 ...]==
命令说明:当前线程订阅channel,如果有其他线程向channel发布消息,那么我们的线程就能收到发布的消息。==阻塞线程==
返回值:订阅的channel数量,还有一些提示信息。
命令:==PSUBSCRIBE pattern [pattern1 ...]==
命令说明:当前线程订阅指定模式的channel,如果有其他线程向满足模式的channel发布消息,那么我们的线程就能收到发布的消息。==阻塞线程==
==在结合前面的例子中:订阅了c:*的channel,在第一个用例中,存在c:test1,c:test2,此时c:*会收到2个channel的消息。==
返回值:订阅的channel模式数量,还有一些提示信息。
命令:==UNSUBSCRIBE channel [channel1 ...]==
命令说明:取消订阅channel。
命令:==PUNSUBSCRIBE pattern [pattern1 ...]==
命令说明:取消订阅channel模式。
5. 发布
命令:==PUBLISH channel message==
命令说明:发布message到channel.
返回值:实际订阅的线程数量。(收到消息的数量)
6. 状态
命令:==PUBSUB CHANNELS [pattern]==
命令说明:列出全部活跃的channel或者符合pattern的channel.
channel活跃指:至少有一个订阅者。
返回值:活跃channel名称。
命令:==PUBSUB NUMSUB [channel1 ....]==
命令说明:获取指定channel的订阅个数。==如果不指定channel,返回空==
返回值:订阅指定channel的数量(活跃的).==输入channel模式为0==
命令:==PUBSUB NUMPAT==
命令说明:获取channel模板个数。
返回值:获取channel模板个数。
7. 作用域
发布/订阅与key所在空间没有关系,它不会受任何级别的干扰,包括不同数据库编码。 发布在db 10,订阅可以在db 1。 如果你需要区分某些频道,可以通过在频道名称前面加上所在环境的名称(例如:测试环境,演示环境,线上环境等)。
8. 资料
官网书籍资料
频道的订阅与信息发送
Redis 的 SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。
作为例子, 下图展示了频道
channel1
, 以及订阅这个频道的三个客户端 ——client2
、client5
和client1
之间的关系:[图片上传失败...(image-76938a-1596192874374)]
当有新消息通过 PUBLISH 命令发送给频道
channel1
时, 这个消息就会被发送给订阅它的三个客户端:[图片上传失败...(image-794f29-1596192874374)]
在后面的内容中, 我们将探讨 SUBSCRIBE 和 PUBLISH 命令的实现, 以及这套订阅与发布机制的运作原理。
订阅频道
每个 Redis 服务器进程都维持着一个表示服务器状态的
redis.h/redisServer
结构, 结构的pubsub_channels
属性是一个字典, 这个字典就用于保存订阅频道的信息:struct redisServer { // ... dict *pubsub_channels; // ... };
其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
比如说,在下图展示的这个
pubsub_channels
示例中,client2
、client5
和client1
就订阅了channel1
, 而其他频道也分别被别的客户端所订阅:[图片上传失败...(image-7bbbc-1596192874374)]
当客户端调用 SUBSCRIBE 命令时, 程序就将客户端和要订阅的频道在
pubsub_channels
字典中关联起来。举个例子,如果客户端
client10086
执行命令SUBSCRIBE channel1 channel2 channel3
,那么前面展示的pubsub_channels
将变成下面这个样子:[图片上传失败...(image-ceebff-1596192874374)]
SUBSCRIBE 命令的行为可以用伪代码表示如下:
def SUBSCRIBE(client, channels): # 遍历所有输入频道 for channel in channels: # 将客户端添加到链表的末尾 redisServer.pubsub_channels[channel].append(client)
通过
pubsub_channels
字典, 程序只要检查某个频道是否为字典的键, 就可以知道该频道是否正在被客户端订阅; 只要取出某个键的值, 就可以得到所有订阅该频道的客户端的信息。发送信息到频道
了解了
pubsub_channels
字典的结构之后, 解释 PUBLISH 命令的实现就非常简单了: 当调用PUBLISH channel message
命令, 程序首先根据channel
定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。比如说,对于以下这个
pubsub_channels
实例, 如果某个客户端执行命令PUBLISH channel1 "hello moto"
,那么client2
、client5
和client1
三个客户端都将接收到"hello moto"
信息:[图片上传失败...(image-1948ef-1596192874374)]
PUBLISH 命令的实现可以用以下伪代码来描述:
def PUBLISH(channel, message): # 遍历所有订阅频道 channel 的客户端 for client in server.pubsub_channels[channel]: # 将信息发送给它们 send_message(client, message)
退订频道
使用 UNSUBSCRIBE 命令可以退订指定的频道, 这个命令执行的是订阅的反操作: 它从
pubsub_channels
字典的给定频道(键)中, 删除关于当前客户端的信息, 这样被退订频道的信息就不会再发送给这个客户端。模式的订阅与信息发送
当使用 PUBLISH 命令发送信息到某个频道时, 不仅所有订阅该频道的客户端会收到信息, 如果有某个/某些模式和这个频道匹配的话, 那么所有订阅这个/这些频道的客户端也同样会收到信息。
下图展示了一个带有频道和模式的例子, 其中
tweet.shop.*
模式匹配了tweet.shop.kindle
频道和tweet.shop.ipad
频道, 并且有不同的客户端分别订阅它们三个:[图片上传失败...(image-e75ebe-1596192874374)]
当有信息发送到
tweet.shop.kindle
频道时, 信息除了发送给clientX
和clientY
之外, 还会发送给订阅tweet.shop.*
模式的client123
和client256
:[图片上传失败...(image-9e6cc8-1596192874374)]
另一方面, 如果接收到信息的是频道
tweet.shop.ipad
, 那么client123
和client256
同样会收到信息:[图片上传失败...(image-ae1877-1596192874374)]
订阅模式
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
链表, 其中client123
和client256
都正在订阅tweet.shop.*
模式:[图片上传失败...(image-daa4ed-1596192874374)]
如果这时客户端
client10086
执行PSUBSCRIBE broadcast.list.*
, 那么pubsub_patterns
链表将被更新成这样:[图片上传失败...(image-4c438e-1596192874374)]
通过遍历整个
pubsub_patterns
链表,程序可以检查所有正在被订阅的模式,以及订阅这些模式的客户端。发送信息到模式
发送信息到模式的工作也是由 PUBLISH 命令进行的, 在前面讲解频道的时候, 我们给出了这样一段伪代码, 说它定义了 PUBLISH 命令的行为:
def PUBLISH(channel, message): # 遍历所有订阅频道 channel 的客户端 for client in server.pubsub_channels[channel]: # 将信息发送给它们 send_message(client, message)
但是,这段伪代码并没有完整描述 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
状态如下:[图片上传失败...(image-eccf4c-1596192874374)]
那么当某个客户端发送信息
"Amazon Kindle, $69."
到tweet.shop.kindle
频道时, 除了所有订阅了tweet.shop.kindle
频道的客户端会收到信息之外, 客户端client123
和client256
也同样会收到信息, 因为这两个客户端订阅的tweet.shop.*
模式和tweet.shop.kindle
频道匹配。退订模式
使用 PUNSUBSCRIBE 命令可以退订指定的模式, 这个命令执行的是订阅模式的反操作: 程序会删除
redisServer.pubsub_patterns
链表中, 所有和被退订模式相关联的pubsubPattern
结构, 这样客户端就不会再收到和模式相匹配的频道发来的信息。