redis 订阅与发布

Reference: https://redisbook.readthedocs.io/en/latest/feature/pubsub.html

Redis 的 SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。

作为例子, 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2client5client1 之间的关系:

redis 订阅与发布

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

redis 订阅与发布

在后面的内容中, 我们将探讨 SUBSCRIBEPUBLISH 命令的实现, 以及这套订阅与发布机制的运作原理。

订阅频道

每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息:

struct redisServer {
    // ...

    dict *pubsub_channels;
    // ...

};

其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

比如说,在下图展示的这个 pubsub_channels 示例中, client2client5client1 就订阅了 channel1 , 而其他频道也分别被别的客户端所订阅:

redis 订阅与发布

当客户端调用 SUBSCRIBE 命令时, 程序就将客户端和要订阅的频道在 pubsub_channels 字典中关联起来。

举个例子,如果客户端 client10086 执行命令 <span class="pre">SUBSCRIBE&#xA0;<span class="pre">channel1&#xA0;<span class="pre">channel2&#xA0;<span class="pre">channel3</span></span></span></span> ,那么前面展示的 pubsub_channels 将变成下面这个样子:

redis 订阅与发布

SUBSCRIBE 命令的行为可以用伪代码表示如下:

def SUBSCRIBE(client, channels):

    # 遍历所有输入频道
    for channel in channels:

        # 将客户端添加到链表的末尾
        redisServer.pubsub_channels[channel].append(client)

通过 pubsub_channels 字典, 程序只要检查某个频道是否为字典的键, 就可以知道该频道是否正在被客户端订阅; 只要取出某个键的值, 就可以得到所有订阅该频道的客户端的信息。

发送信息到频道

了解了 pubsub_channels 字典的结构之后, 解释 PUBLISH 命令的实现就非常简单了: 当调用 <span class="pre">PUBLISH&#xA0;<span class="pre">channel&#xA0;<span class="pre">message</span></span></span> 命令, 程序首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。

比如说,对于以下这个 pubsub_channels 实例, 如果某个客户端执行命令 <span class="pre">PUBLISH&#xA0;<span class="pre">channel1&#xA0;<span class="pre">"hello&#xA0;<span class="pre">moto"</span></span></span></span> ,那么 client2client5client1 三个客户端都将接收到 <span class="pre">"hello&#xA0;<span class="pre">moto"</span></span> 信息:

redis 订阅与发布

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 频道, 并且有不同的客户端分别订阅它们三个:

redis 订阅与发布

当有信息发送到 tweet.shop.kindle 频道时, 信息除了发送给 clientXclientY 之外, 还会发送给订阅 tweet.shop.* 模式的 client123client256

redis 订阅与发布

另一方面, 如果接收到信息的是频道 tweet.shop.ipad , 那么 client123client256 同样会收到信息:

redis 订阅与发布

订阅模式

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 链表, 其中 client123client256 都正在订阅 tweet.shop.* 模式:

redis 订阅与发布

如果这时客户端 client10086 执行 <span class="pre">PSUBSCRIBE&#xA0;<span class="pre">broadcast.list.*</span></span> , 那么 pubsub_patterns 链表将被更新成这样:

redis 订阅与发布

通过遍历整个 pubsub_patterns 链表,程序可以检查所有正在被订阅的模式,以及订阅这些模式的客户端。

发送信息到模式

发送信息到模式的工作也是由 PUBLISH 命令进行的, 在前面讲解频道的时候, 我们给出了这样一段伪代码, 说它定义了 PUBLISH 命令的行为:

def PUBLISH(channel, message):

    # 遍历所有订阅频道 channel 的客户端
    for client in server.pubsub_channels[channel]:

        # 将信息发送给它们
        send_message(client, message)

但是,这段伪代码并没有完整描述 PUBLISH 命令的行为, 因为 PUBLISH 除了将 message 发送到所有订阅 channel 的客户端之外, 它还会将 channelpubsub_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 状态如下:

redis 订阅与发布

那么当某个客户端发送信息 <span class="pre">"Amazon&#xA0;<span class="pre">Kindle,&#xA0;<span class="pre">$69."</span></span></span>tweet.shop.kindle 频道时, 除了所有订阅了 tweet.shop.kindle 频道的客户端会收到信息之外, 客户端 client123client256 也同样会收到信息, 因为这两个客户端订阅的 tweet.shop.* 模式和 tweet.shop.kindle 频道匹配。

退订模式

使用 PUNSUBSCRIBE 命令可以退订指定的模式, 这个命令执行的是订阅模式的反操作: 程序会删除 redisServer.pubsub_patterns 链表中, 所有和被退订模式相关联的 pubsubPattern 结构, 这样客户端就不会再收到和模式相匹配的频道发来的信息。

小结

  • 订阅信息由服务器进程维持的 redisServer.pubsub_channels 字典保存,字典的键为被订阅的频道,字典的值为订阅频道的所有客户端。
  • 当有新消息发送到频道时,程序遍历频道(键)所对应的(值)所有客户端,然后将消息发送到所有订阅频道的客户端上。
  • 订阅模式的信息由服务器进程维持的 redisServer.pubsub_patterns 链表保存,链表的每个节点都保存着一个 pubsubPattern 结构,结构中保存着被订阅的模式,以及订阅该模式的客户端。程序通过遍历链表来查找某个频道是否和某个模式匹配。
  • 当有新消息发送到频道时,除了订阅频道的客户端会收到消息之外,所有订阅了匹配频道的模式的客户端,也同样会收到消息。
  • 退订频道和退订模式分别是订阅频道和订阅模式的反操作。

Original: https://www.cnblogs.com/skying555/p/11330308.html
Author: alex.shu
Title: redis 订阅与发布

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/529244/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • QT父类信号调用子类槽函数

    故事背景:我定义了一个QT父类,里面有自定义信号和槽函数,我在main函数定义父类指针指向子类对象,此时发信号,子类收不到,开始咱们的探险之路。。。 解决方案:根据加断点发现调用的…

    Linux 2023年6月13日
    082
  • DQL

    查询语法 select 字段列表 from 表名列表 where 条件列表 group by 分组字段 having 分组后条件 order by 排序字段 limit 分页限定 …

    Linux 2023年6月7日
    077
  • 【原创】Linux虚拟化KVM-Qemu分析(十一)之virtqueue

    背景 Read the fucking source code! –By 鲁迅 A picture is worth a thousand words. –…

    Linux 2023年6月8日
    086
  • 关闭linux内核反向路由

    route -n Kernel IP routing tableDestination Gateway Genmask Flags Metric Ref Use Iface0.0….

    Linux 2023年6月8日
    0106
  • 数据结构 单链表

    cpp;gutter:true;</p> <h1>include</h1> <h1>define null 0;</h1&gt…

    Linux 2023年6月13日
    083
  • Python3中datetime不同时区转换介绍与踩坑

    最近的项目需要根据用户所属时区制定一些特定策略,学习、应用了若干python3的时区转换相关知识,这里整理一部分记录下来。 下面涉及的几个概念及知识点: GMT时间:Greenwi…

    Linux 2023年6月6日
    098
  • 关系型、非关系型数据库存储选型盘点大全

    工作中总是遇到数据存储相关的 Bug 工单,新需求开发设计中也多多少少会有数据模型设计和存储相关的问题。经过几次存储方案设计选型和讨论后发现需要有更全面的思考框架。 日常开发中常用…

    Linux 2023年6月8日
    0112
  • 操作系统

    第1章习题1. 设计现代OS的主要目标是什么? OS的作用可表现在哪几个方面? 为什么说操作系统实现了对计算机资源的抽象? 试说明推动多道批处理系统形成和发展的主要动力是什么。 何…

    Linux 2023年6月6日
    089
  • flask的使用

    python网站开发框架: django:大而全 flask:小而精 flask的web服务器:werkzeug 模板语法: jinjia2,兼容dtl 登录案例: from fl…

    Linux 2023年6月14日
    0103
  • 简单交叉编译学习

    交叉编译 交叉编译是在一个平台上生成另一个平台上的可执行代码。 同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。通常是自己的电脑写好代码编译…

    Linux 2023年6月7日
    0131
  • 解决vscode+python不提示numpy函数的问题

    使用vscode编写numpy代码时,对于numpy.array()等方法总是无法提示。查找了很多博客后,大部分都是修改配置和安装多种vscode插件,经过尝试后方法对于我来说无效…

    Linux 2023年6月7日
    098
  • Log4j 日志框架

    Log4j(Log for java)是 Apache 的一个开源项目,通过使用 Log4j,可以控制日志信息输送的目的地是控制台或文件等,也可以控制每一条日志的输出格式。通过定义…

    Linux 2023年6月8日
    0135
  • MySQL manager or server PID file could not be found!

    [root@centos var]# service mysqld stop MySQL manager or server PID file could not be found…

    Linux 2023年6月13日
    087
  • 安装Redis6.x

    gcc安装完毕以后,接下来可以按照redis了,解压redis6安装包 tar -zxvf redis-6.0.9.tar.gz * 查看日志,表示运行成功 vim /usr/lo…

    Linux 2023年5月28日
    077
  • 剑指offer计划21( 位运算简单)—java

    1.1、题目1 剑指 Offer 15. 二进制中1的个数 1.2、解法 通过判断每一位的与来识别1的数量。 1.3、代码 public class Solution { // y…

    Linux 2023年6月11日
    0135
  • [PYTHON][BAT][SHELL] 常见易忘 python、bat、shell 脚本操作汇总(持续更新)

    BAT 脚本 1、相互调用 1.1、bat 调用 python 1.2、bat 调用 bat 2、系统相关 2.1、不关闭 2.2、读取环境变量 3、文件操作 3.1、读取 3.2…

    Linux 2023年6月8日
    0170
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球