Redis设计与实现 学习笔记 第十八章 发布与订阅

第18到24章是本书第四部分:独立功能的实现。

Redis的发布与订阅功能由PUBLISH、SUBSCRIBE、PSUBSCRIBE等命令组成。

通过执行SUBSCRIBE命令,客户端可订阅一个或多个频道,从而成为这些频道的订阅者(subscriber):每当有其他客户端向被订阅的频道发送消息(message)时,频道的所有订阅者都会收到这条消息。

例如,A、B、C三个客户端都执行了命令:
在这里插入图片描述
那么这三个客户端就是"news.it"频道的订阅者,如图18-1所示:
在这里插入图片描述
如果这时某个客户端执行命令:
在这里插入图片描述
向"news.it"频道发送消息"hello",那么"new.it"的三个订阅者都将收到这条消息,如图18-2所示:
在这里插入图片描述
除了订阅频道之外,客户端还可通过执行PSUBSCRIBE命令订阅一个或多个模式,从而成为这些模式的订阅者:每当有其他客户端向某个频道发送消息时,消息不仅会被发送给这个频道的所有订阅者,还会被发送给所有与这个频道相匹配的模式的订阅者。

例如,如图18-3所示:
在这里插入图片描述
上图中:
1.客户端A正在订阅频道"news.it"。

2.客户端B正在订阅频道"news.et"。

3.客户端C和D正在订阅与频道"news.it"和"news.et"相匹配的模式"news.[ie]t"。

如果这时某个客户端执行命令:
在这里插入图片描述
那么客户端A、C、D都会收到消息,如图18-4所示:
在这里插入图片描述
18.1 频道的订阅与退订

Redis将所有频道的订阅关系都保存在服务器状态的pubsub_channels字典里,这个字典的键是某个被订阅的频道,而键的值则是一个链表,链表里记录了所有订阅这个频道的客户端:

struct redisServer {// ...// 保存所有频道的订阅关系dict *pubsub_channels;// ...
};

比如,图18-6展示了一个pubsub_channels字典示例:
在这里插入图片描述
上图字典中记录了以下信息:
1.client-1、client-2、client-3三个客户端正在订阅"news.it"频道。

2.客户端client-4正在订阅"news.sport"频道。

3.client-5、client-6两个客户端正在订阅"news.business"频道。

18.1.1 订阅频道

每当客户执行SUBSCRIBE命令订阅某个或某些频道的时候,服务器都会将客户端与被订阅的频道在pubsub_channels字典中进行关联。

根据频道是否已经有其他订阅者,关联操作分为两种情况执行:
1.如果频道已经有其他订阅者,那么它在pubsub_channels字典中必然有相应的订阅者链表,程序唯一要做的就是将客户端添加到订阅者链表末尾。

2.如果频道中还未有任何订阅者,那么它必然不存在于pubsub_channels字典,程序首先要在pubsub_channels字典中为频道创建一个键,并将这个键的值设置为空链表,然后再将客户端添加到链表,成为链表的第一个元素。

例如,服务器pubsub_channels字典的当前状态如图18-6所示,那么当客户端client-10086执行命令:
在这里插入图片描述
之后,pubsub_channels字典将更新至图18-7所示的状态,其中用虚线包围的是新添加的节点:
在这里插入图片描述
上图中:
1.更新后的pubsub_channels字典新增了"news.movie"键,该键对应的链表值只包含一个client-10086节点,表示目前只有client-10086一个客户端在订阅"news.movie"频道。

2.至于原本就已经有客户端在订阅的"news.sport"频道,client-10086的节点放在了频道对应链表的末尾,排在client-4节点的后面。

SUBSCRIBE命令的实现可用以下伪代码来描述:

def subscribe(*all_input_channels):# 遍历输入的所有频道for channel in all_input_channels:# 如果channel不存在于pubsub_channels字典(该channel没有任何订阅者)# 那么在字典中添加channel键,并设置它的值为空链表if channel not in server.pubsub_channels:server.pubsub_channels[channel] = []# 将订阅者添加到频道对应的链表末尾server.pubsub_channels[channel].append(client)

18.1.2 退订频道

UNSUBSCRIBE命令的行为和SUBSCRIBE命令的行为正好相反,当一个客户端退订某个或某些频道时,服务器将从pubsub_channels中解除客户端与被退订频道之间的关联:
1.程序会根据被退订频道的名字,在pubsub_channels字典中找到频道对应的订阅者链表,然后从订阅者链表中删除退订客户端的信息。

2.如果删除退订客户端后,频道的订阅者链表变成了空链表,那么说明这个频道已经没有任何订阅者了,程序将从pubsub_channels字典中删除频道对应的键。

例如,pubsub_channels的当前状态如图18-8所示:
在这里插入图片描述
那么当客户端client-10086执行命令:
在这里插入图片描述
之后,图18-8中用虚线包围的两个节点将被删除,如图18-9所示:
在这里插入图片描述
上图中:
1.在pubsub_channels字典更新后,client-10086的信息已经从"news.sport"和"news.movie"频道的订阅者链表中删除了。

2.另外,因为删除client-10086后,频道"news.movie"已经没有任何订阅者,因此键"news.movie"也从字典中被删除了。

UNSUBSCRIBE命令的实现可用以下伪代码来描述:

def unsubscribe(*all_input_channels):# 遍历要退订的所有频道for channel in all_input_channels:# 在订阅者链表中删除退订的客户端server.pubsub_channels[channel].remove(client)# 如果频道已经没有任何订阅者了(订阅者链表为空)# 那么将频道从字典中删除if len(server.pubsub_channels[channel]) == 0:server.pubsub_channels.remove(channel)

18.2 模式的订阅与退订

服务器将所有模式的订阅关系都保存在服务器状态的pubsub_patterns属性里:

struct redisServer {// ...// 保存所有模式订阅关系list *pubsub_patterns;// ...
};

pubsub_patterns属性是一个链表,链表中的每个节点都包含着一个pubsubPattern结构:

typedef struct pubsubPattern {// 订阅模式的客户端redisClient *client;// 被订阅的模式robj *pattern;
} pubsubPattern;

图18-10是一个pubsubPattern结构示例,它显示客户端client-9正在订阅模式"news.*"。

图18-11展示了一个pubsub_patterns链表示例:
在这里插入图片描述
上图中:
1.客户端client-7正在订阅模式"music.*"。

2.客户端client-8正在订阅模式"book.*"。

3.客户端client-9正在订阅模式"news.*"。

18.2.1 订阅模式

每当客户端执行PSUBSCRIBE命令订阅某个或某些模式时,服务器会对每个被订阅的模式执行以下两个操作:
1.新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式,client属性设置为订阅模式的客户端。

2.将pubsubPattern结构添加到pubsub_patterns链表的表尾。例如,假设服务器中pubsub_patterns链表的当前状态如图18-12所示:
在这里插入图片描述
那么当客户端client-9执行命令:
在这里插入图片描述
之后,pubsub_patterns链表将更新至图18-13所示的状态,其中用虚线包围的是新添加的pubsubPattern结构:
在这里插入图片描述
PSUBSCRIBE命令的实现原理可用以下伪代码来描述:

def psubscribe(*all_input_patterns):# 遍历输入的所有模式for pattern in all_input_patterns:# 创建新的pubsubPattern结构# 记录被订阅的模式,以及订阅模式的客户端pubsubPattern = create_new_pubsubPattern()pubsubPattern.client = clientpubsubPattern.pattern = pattern# 将新的pubsubPattern追加到pubsub_patterns链表末尾server.pubsub_patterns.append(pubsubPattern)

18.2.2 退订模式

模式的退订命令PUNSUBSCRIBE是PSUBSCRIBE命令的反操作:当一个客户端退订某个或某些模式时,服务器将在pubsub_patterns链表中查找并删除那些pattern属性为被退订模式,且client属性为执行退订命令的客户端的pubsubPattern结构。

例如,服务器pubsub_patterns链表的当前状态如图18-14所示:
在这里插入图片描述
那么当客户端client-9执行命令:
在这里插入图片描述
之后,client属性为client-9,pattern属性为"news.*"的pubsubPattern结构将被删除,pubsub_patterns链表将更新至图18-15所示的样子:
在这里插入图片描述
PUNSUBSCRIBE命令的实现原理可用以下伪代码来描述:

def punsubscribe(*all_input_patterns):# 遍历所有要退订的模式for pattern in all_input_patterns:# 遍历pubsub_patterns链表中的所有pubsubPattern结构for pubsubPattern in server.pubsub_patterns:# 如果当前客户端和pubsubPattern记录的客户端相同# 并且要退订的模式也和pubsubPattern记录的模式相同if client == pubsubPattern.client and pattern == pubsubPattern.pattern:# 那么将这个pubsubPattern从链表中删除server.pubsub_patterns.remove(pubsubPattern)

18.3 发送消息

当一个Redis客户端执行PUBLISH <channel> <message>命令将消息message发送给频道channel时,服务器需要执行以下两个动作:
1.将消息message发送给channel频道的所有订阅者。

2.如果有一个或多个模式pattern与频道channel相匹配,那么将消息message发送给pattern模式的订阅者。

18.3.1 将消息发送给频道订阅者

因为服务器状态中的pubsub_channels字典记录了所有频道的订阅关系,所以为了将消息发送给channel频道的所有订阅者,PUBLISH命令要做的就是在pubsub_channels字典里找到频道channel的订阅者名单(一个链表),然后将消息发送给名单上的所有客户端。例如,服务器pubsub_channels字典当前的状态如图18-16所示:
在这里插入图片描述
如果这时某个客户端执行命令:
在这里插入图片描述
那么PUBLISH命令将在pubsub_channels字典中查找键"news.it"对应的链表值,并通过遍历链表将消息"hello"发送给"news.it"频道的三个订阅者:client-1、client-2、client-3。

PUBLISH命令将消息发送给频道订阅者的方法可以用以下伪代码来描述:

def channel_publish(channel, message):# 如果channel键不存在于pubsub_channels字典中# 那么说明channel频道没有任何订阅者# 程序不做发送动作,直接返回if channel not in server.pubsub_channels:return# 运行到这里,说明channel频道至少有一个订阅者# 程序遍历channel频道的订阅者链表# 将消息发送给所有订阅者for subscriber in server.pubsub_channels[channel]:send_message(subscriber, message)

18.3.2 将消息发送给模式订阅者

因为服务器状态中的pubsub_patterns链表记录了所有模式的订阅关系,所以为了将消息发送给所有与channel频道相匹配的模式的订阅者,PUBLISH命令要做的就是遍历整个pubsub_patterns链表,查找那些与channel频道相匹配的模式,并将消息发送给订阅了这些模式的客户端。

例如,pubsub_patterns链表的当前状态如图18-17所示:
在这里插入图片描述
如果这时某个客户端执行命令:
在这里插入图片描述
那么PUBLISH命令会首先将消息"hello"发送给"news.it"频道的所有订阅者,然后开始在pubsub_patterns链表中查找是否有被订阅的模式与"news.it"频道相匹配,结果发现"news.it"频道和客户端client-9订阅的"news.*"模式匹配,于是命令将消息"hello"发送给客户端client-9。

PUBLISH命令将消息发送给模式订阅者的方法可以用以下伪代码来描述:

def pattern_publish(channel, message):# 遍历所有模式订阅消息for pubsubPattern in server.pubsub_patterns:# 如果频道和模式相匹配if match(channel, pubsubPattern.pattern):# 那么将消息发送给订阅该模式的客户端send_message(pubsubPattern.client, message)

最后,PUBLISH命令的实现可用以下伪代码来描述:

def publish(channel, message):# 将消息发送给channel频道的所有订阅者channel_publish(channel, message)# 将消息发送给所有和channel频道相匹配的模式的订阅者pattern_publish(channel, message)

18.4 查看订阅信息

PUBSUB命令是Redis 2.8新增加的命令之一,客户端可通过这个命令来查看频道或模式的相关信息,比如某个频道或模式目前有多少订阅者等。

18.4.1 PUBSUB CHANNELS

PUBSUB CHANNELS [pattern]子命令用于返回服务器当前被订阅的频道,其中pattern参数是可选的:
1.如果不给定pattern参数,那么命令返回服务器当前被订阅的所有频道。

2.如果给定pattern参数,那么命令返回服务器当前被订阅的频道中那些与pattern模式相匹配的频道。

这个子命令是通过遍历服务器的pubsub_channels字典的所有键(每个键都是一个被订阅的频道),然后记录并返回所有符合条件的频道来实现的,这个过程可用以下伪代码来描述:

def pubsub_channels(pattern=None):# 一个列表,用于记录所有符合条件的频道channel_list = []# 遍历服务器中的所有频道# (即pubsub_channels字典的所有键)for channel in server.pubsub_channels:# 当以下两个条件的任意一个满足时,将频道添加到链表里#1)用户没有指定pattern参数#2)用户指定了pattern参数,且channel和pattern匹配if (pattern is None) or match(channel, pattern):channel_list.append(channel)# 向客户端返回频道列表return channel_list

例如,对于图18-18所示的pubsub_channels字典:
在这里插入图片描述
执行PUBSUB CHANNELS命令将返回服务器目前被订阅的四个频道:
在这里插入图片描述
另一方面,执行PUBSUB CHANNELS "news.[is]*"命令将返回"news.it"和"news.sport"两个频道,因为只有这两个频道和"news.[is]*"模式相匹配:
在这里插入图片描述
18.4.2 PUBSUB NUMSUB

PUBSUB NUMSUB [channel-1 channel-2 ... channel-n]子命令接受任意多个频道作为输入参数,并返回这些频道的订阅者数量。

这个子命令是通过在pubsub_channels字典中找到频道对应的订阅者链表,然后返回订阅者链表的长度来实现的(订阅者链表的长度就是频道订阅者的数量),这个过程可用以下伪代码来描述:

def pubsub_numsub(*all_input_channels):# 遍历输入的所有频道for channel in all_input_channels:# 如果pubsub_channels字典中没有channel这个键# 那么说明channel频道没有任何订阅者if channel not in server.pubsub_channels:# 返回频道名reply_channel_name(channel)reply_subscribe_count(0)# 如果pubsub_channels字典中存在channel键# 那么说明channel频道至少有一个订阅者else:# 返回频道名reply_channel_name(channel)# 订阅者链表的长度就是订阅者数量reply_subscribe_count(len(server.pubsub_channels[channel]))

例如,对于图18-19所示的pubsub_channels字典来说:
在这里插入图片描述
对字典中的四个频道执行PUBSUB NUMSUB命令将获得以下回复:
在这里插入图片描述
18.4.3 PUBSUB NUMPAT

PUBSUB NUMPAT子命令用于返回服务器当前被订阅模式的数量。

这个子命令是通过返回pubsub_patterns链表的长度来实现的,因为这个链表的长度就是服务器被订阅模式的数量,这个过程可用以下伪代码来描述:

def pubsub_numpat():# pubsub_patterns链表的长度就是被订阅模式的数量reply_pattern_count(len(server.pubsub_patterns))

例如,对于图18-20所示的pubsub_patterns链表来说:
在这里插入图片描述
执行PUBSUB NUMPAT命令将返回3:
在这里插入图片描述
18.5 重点回顾

1.服务器状态在pubsub_channels字典保存了所有频道的订阅关系:SUBSCRIBE命令负责将客户端和被订阅的频道关联到这个字典里,而UNSUBSCRIBE命令则负责解除客户端和被退订频道之间的关联。

2.服务器状态在pubsub_patterns链表保存了所有模式的订阅关系:PSUBSCRIBE命令负责将客户端和被订阅的模式记录到这个链表中,而PUNSUBSCRIBE命令则负责移除客户端和被退订模式在链表中的记录。

3.PUBLISH命令通过访问pubsub_channels字典来向频道的所有订阅者发送消息,通过访问pubsub_patterns链表来向所有匹配频道的模式的订阅者发送消息。

4.PUBSUB命令的三个子命令都是通过读取pubsub_channels字典和pubsub_patterns链表中的信息来实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/18240.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

小程序-基于java+SpringBoot+Vue的驾校预约平台设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

python多版本管理 windows11 pyenv

前言 需要开发多个项目&#xff0c;但各个项目的版本不一致怎么办&#xff1f;python -m venv 只解决了依赖隔离问题&#xff0c;但venv本身并没有办法提供多个python版本。因此我们要引入pyenv来解决。 安装pyenv https://pyenv-win.github.io/pyenv-win/ 安装很简单&…

01.防火墙概述

防火墙概述 防火墙概述1. 防火墙的分类2. Linux 防火墙的基本认识3. netfilter 中五个勾子函数和报文流向 防火墙概述 防火墙&#xff08; FireWall &#xff09;&#xff1a;隔离功能&#xff0c;工作在网络或主机边缘&#xff0c;对进出网络或主机的数据包基于一定的 规则检…

Excel表格解析为QTableWidget

解析表格 头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QAxObject> #include <QTableWidget> #include <QTableWidgetItem> #include <QDebug> #include <QSet> #include <QPoint> #include…

华为欧拉系统使用U盘制作引导安装华为欧拉操作系统

今天记录一下通过U盘来安装华为欧拉操作系统 华为欧拉操作系统是国产的一个类似于Centos的Linus系统 具体实现操作步骤&#xff1a; 先在官网下载欧拉系统镜像点击跳转到下载 准备好一个大于16g的U盘 &#xff0c;用于制作U盘启动 下载一个引导程序制作工具&#xff0c;我使用…

魔改log4j2的JsonLayout,支持自定义json格式日志

小伙伴们&#xff0c;你们好&#xff0c;我是老寇&#xff0c;我又回来辣&#xff0c;1个多月不见甚是想念啊&#xff01;&#xff01;&#xff01;跟我一起魔改源码吧 1.自定义json格式【PatternLayout】 大部分教程都是这个&#xff0c;因此&#xff0c;我就简单给个配置&a…

机器学习—学习曲线

学习曲线是帮助理解学习算法如何工作的一种方法&#xff0c;作为它所拥有的经验的函数。 绘制一个符合二阶模型的学习曲线&#xff0c;多项式或二次函数&#xff0c;画出交叉验证错误Jcv&#xff0c;以及Jtrain训练错误&#xff0c;所以在这个曲线中&#xff0c;横轴将是Mtrai…

在MATLAB中实现自适应滤波算法

自适应滤波算法是一种根据信号特性自动调整滤波参数的数字信号处理方法&#xff0c;其可以有效处理噪声干扰和信号畸变问题。在许多实时数据处理系统中&#xff0c;自适应滤波算法得到了广泛应用。在MATLAB中&#xff0c;可以使用多种方法实现自适应滤波算法。本文将介绍自适应…

【系统编程】实验7 消息队列

设计程序 使用消息队列实现两个进程之间的信息互通 snd.c #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/msg.h> #include <unistd.h>/*消息发送者 */// 消息结构体如下&#xff1a; …

ETH钱包地址如何获取 如何购买比特币

首先我们要先注册一个交易所 Gate.io&#xff08;推荐&#xff09;: 点我注册 1、注册很简单&#xff0c;通过手机号就可以进行注册了。 2、获取ETH钱包地址 注册好之后&#xff0c;如图所示&#xff0c;点击“统一账户” 3、通过搜索栏搜索ETH&#xff0c;如下图所示 4、点…

[Docker#11] 容器编排 | .yml | up | 实验: 部署WordPress

目录 1. 什么是 Docker Compose 生活案例 2. 为什么要使用 Docker Compose Docker Compose 的安装 Docker Compose 的功能 使用步骤 核心功能 Docker Compose 使用场景 Docker Compose 文件&#xff08;docker-compose.yml&#xff09; 模仿示例 文件基本结构及常见…

OpenCV基础(1)

1.图像读写与窗口显示 1.1.imread读取图像文件 Mat cv::imread(const string &filename,int flags IMREAD_COLOR); filename&#xff1a;要读取的图像文件名flags&#xff1a;读取模式&#xff0c;可以从枚举cv::ImreadModes中取值&#xff0c;默认取值是IMREAD_COLOR&am…

【优选算法篇】分治乾坤,万物归一:在重组中窥见无声的秩序

文章目录 分治专题&#xff08;二&#xff09;&#xff1a;归并排序的核心思想与进阶应用前言、第二章&#xff1a;归并排序的应用与延展2.1 归并排序&#xff08;medium&#xff09;解法&#xff08;归并排序&#xff09;C 代码实现易错点提示时间复杂度和空间复杂度 2.2 数组…

生产环境centos8 Red Hat8部署ansible and 一键部署mysql两主两从ansible脚本预告

一、各节点服务器创建lvm逻辑卷组 1.初始化磁盘为物理卷&#xff08;PV&#xff09; 命令&#xff1a;sudo pvcreate /dev/vdb 2.创建卷组&#xff08;VG&#xff09; 命令&#xff1a;sudo vgcreate db_vg /dev/vdb 3.创建逻辑卷&#xff08;LV&#xff09; 命令&#xff1a;s…

CNN神经网络

CNN 一 基本概述二 基础知识三 经典案例 今天跟大家聊聊人工智能中的神经网络模型相关内容。神经网络内容庞大,篇幅有限本文主要讲述其中的CNN神经网络模型。 一 基本概述 深度学习(Deep Learning)特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网…

【Ubuntu24.04】VirtualBox安装ubuntu-live-server24.04

目录 0 背景1 下载镜像2 安装虚拟机3 安装UbuntuServer24.044 配置基本环境5 总结0 背景 有了远程连接工具之后,似乎作为服务器的Ubuntu24.04桌面版有点备受冷落了,桌面版的Ubuntu24.04的优势是图形化桌面,是作为一个日常工作的系统来用的,就像Windows,如果要作为服务器来…

【策略模式】最佳实践——Spring IoC实现策略模式全流程深度解析

简介 策略模式是一种行为型设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每一个算法封装起来&#xff0c;使它们可以互相替换&#xff0c;并且使算法的变化不会影响使用算法的客户端。策略模式通过将具体的业务逻辑从上下文&#xff08;Context&#xff09;中剥离出…

企业项目级IDEA设置类注释、方法注释模板(仅增加@author和@date)

文章目录 前言一 设置类注释1.1 添加模板1.2 复制配置 二 设置方法注释2.1 添加模版2.2 设置模版2.3 设置参数变量2.4 配置对应快捷键2.5 配置对应作用域2.6 使用方式 说明 前言 公司代码规范中&#xff0c;需要在标准JavaDoc注释的基础上加上作者和日期。网上虽然有很多现成的…

单片机学习笔记 2. LED灯闪烁

目录 0、实现的功能 1、Keil工程 2、代码实现 0、实现的功能 LED灯闪烁 1、Keil工程 闪烁原理&#xff1a;需要进行软件延时达到人眼能分辨出来的效果。常用的延时方法有软件延时和定时器延时。此次先进行软件延时 具体操作步骤和之前的笔记一致。此次主要利用无符号整型的范…

编辑器vim 命令的学习

1.编辑器Vim 1.vim是一个专注的编辑器 2.是一个支持多模式的编辑器 1.1见一见&#xff1a; vim 的本质也是一条命令 退出来&#xff1a;-> Shift:q 先创建一个文件 再打开这个文件 进入后先按 I 然后就可以输入了 输入完后&#xff0c;保存退出 按Esc --> 来到最后一…