Redis Pub/Sub模式:分布式系统中的解耦利器

news2024/11/15 10:46:05

序言

Redis的发布订阅(Pub/Sub)是一种消息通信模式,允许发布者(Publisher)发送消息到频道(Channel),而订阅者(Subscriber)可以订阅一个或多个频道来接收消息。

Redis 的发布订阅功能命令:PUBLISHSUBSCRIBEPSUBSIRIBE 等。

基础使用

频道订阅

执行SUBSCRIBE命令,客户端可以订阅一个或多个频道,从而成为这些频道的订阅者,当其他客户端向被订阅的频道发送消息时,频道的所有订阅者都会受到该消息。
在这里插入图片描述
假设客户端 A 和客户端 B 都执行了SUBSCRIBE news.it,那么他们就是频道news.it的订阅者。

消息发布

假设客户端 C执行 PUBLISH news.it hello,向频道news.it发送消息,那么客户端 A 和客户端 B 都会收到消息。
在这里插入图片描述

频道退订

当客户端 B 执行UNSUBSCRIBE news.it命令,则它与频道之间的订阅关系就被解除了,当该频道再有消息时,客户端 B 就收不到消息了。
在这里插入图片描述

实现原理

SUBSCRIBE

当客户端执行 SUBSCRIBE 命令订阅某个频道的时候,则客户端就与被订阅的频道之间建立了一种订阅关系。
Redis 是将所有频道的订阅关系都保存在服务器状态的pubsub_channels 字典里。

  • 键:被订阅频道的名称;
  • 值:一个链表,记录了所有订阅这个频道的客户端。
    在这里插入图片描述
    比如上图字典就记录了
  • client-1、client-2、client-3 订阅了 news.it 频道;
  • client-4 订阅了 news.sport 频道;
  • client-5、client-6 订阅了 news.business 频道。

那么在内部,这些订阅关系是怎么维护的呢?
根据频道是否有其他订阅者,分为两种情况:

  1. 如果频道有其他订阅者,那么它在 pubsub_channel 字典中必然有相对应的订阅者链表,那么就将新客户端添加到链表的尾部;
  2. 如果频道没有订阅者,那么它在 pubsub_channel 字典中就不存在,首先要在 pubsub_channel 字典中为该频道创建一个键,并将这个键的值设置为空链表,然后将客户端添加到链表的头节点。

伪代码:

public class SubscribeDemo {
    private static Map<String, LinkedList> map = new HashMap<>();
    public static void main(String[] args) {
        subscribe(Arrays.asList("news.it", "news.support"), "c1");
        subscribe(Arrays.asList("news.it"), "c2");
        map.forEach((k,v) -> {
            System.out.println("频道:" + k +" 订阅者:" +v);
        });
    }

    private static void subscribe(List<String> channels, String client) {
        for (String channel : channels) {
            if (map.containsKey(channel)) {
                LinkedList linkedList = map.get(channel);
                linkedList.addLast(client);
            } else {
                LinkedList<Object> node = new LinkedList<>();
                node.addFirst(client);
                map.put(channel, node);
            }
        }
    }
}

在这里插入图片描述

UNSUBSCRIBE

当客户端退订频道的时候,服务器将从pubsub_channels中解除客户端与被退订频道之间的关联。

  1. 根据被退订频道的名字,从字典中找到对应的订阅者链表,从链表中删除退订客户端的信息;
  2. 删除退订客户端后,如果频道的订阅者链表为空,则说明该频道已经没有任何订阅者了,则从字典中删除该频道信息。

比如此时客户端 4 执行 UNSUBSCRIBE news.sport
在这里插入图片描述
那么执行之后的字典信息是
在这里插入图片描述
伪代码:

private static void unsubscribe(List<String> channels, String client) {
        for (String channel : channels) {
            System.out.println(client + " 退定频道:" + channel);
            if (map.containsKey(channel)) {
                LinkedList subscribers = map.get(channel); // 该频道的订阅者列表
                int index = subscribers.indexOf(client); // 查找退订的客户端
                subscribers.remove(index); // 从链表中删除
                if (subscribers.isEmpty()) {
                    //如果该频道的订阅者为空,则从字典中删除
                    map.remove(channel);
                }
            }
        }
    }

PUBLISH

当客户端执行PUBLISH <channel> <message> 命令将消息发送给频道channel 的时候,则服务器需要执行以下操作:

  1. 将消息message发送给channel 频道的所有订阅者;
private static void publish(String channel, String message) {
        if (!map.containsKey(channel)) {
            return;
        }
        LinkedList nodes = map.get(channel);
        for (Object client : nodes) {
            System.out.println("给客户端:" + client + " 发送消息:" + message);
        }
    }

完整伪代码

public class SubscribeDemo {

    private static Map<String, LinkedList> map = new HashMap<>();

    public static void main(String[] args) {

        subscribe(Arrays.asList("news.it", "news.support"), "c1");
        subscribe(Arrays.asList("news.it"), "c2");

        map.forEach((k, v) -> {
            System.out.println("频道:" + k + " 订阅者:" + v);
        });

        System.out.println("-------------------");

        unsubscribe(Arrays.asList("news.support"), "c1");

        map.forEach((k, v) -> {
            System.out.println("频道:" + k + " 订阅者:" + v);
        });

        System.out.println("-------------------");

        publish("news.it", "测试消息");

    }

    private static void subscribe(List<String> channels, String client) {

        for (String channel : channels) {
            if (map.containsKey(channel)) {
                //如果订阅的频道在字典中,则将新的客户端添加到链表的尾部
                LinkedList linkedList = map.get(channel);
                linkedList.addLast(client);
            } else {
                //如果订阅的频道在字典中不存在,则将新的客户端添加的链表的头部
                LinkedList<Object> node = new LinkedList<>();
                node.addFirst(client);
                map.put(channel, node);
            }
        }
    }

    private static void unsubscribe(List<String> channels, String client) {
        for (String channel : channels) {
            System.out.println(client + " 退定频道:" + channel);
            if (map.containsKey(channel)) {
                LinkedList subscribers = map.get(channel); // 该频道的订阅者列表

                int index = subscribers.indexOf(client); // 查找退订的客户端

                subscribers.remove(index); // 从链表中删除

                if (subscribers.isEmpty()) {
                    //如果该频道的订阅者为空,则从字典中删除
                    map.remove(channel);
                }
            }
        }
    }

    private static void publish(String channel, String message) {
        //如果频道不在字典中,返回
        if (!map.containsKey(channel)) { 
            return;
        }
        LinkedList nodes = map.get(channel);

        //遍历频道的订阅者列表,并发送消息
        for (Object client : nodes) {
            System.out.println("给客户端:" + client + " 发送消息:" + message);

        }
    }
}

在这里插入图片描述

使用场景

  • 实时消息系统:如聊天应用、新闻更新推送;
  • 事件通知:如用户行为触发的通知,订单状态变更通知;
  • 分布式系统中的数据同步:如数据库的主从复制状态同步。

优缺点

优点

  • 简单易用:通过简单的命令即可实现发布和订阅功能;
  • 低延迟:消息传递速度快,适用于需要快速响应的场景;
  • 可扩展性:可以轻松地添加更多的订阅者。

缺点

  • 消息无持久化:Redis不会存储发布的消息,如果订阅者不在线,将错过消息;
  • 资源消耗:每个订阅者都需要维护一个与Redis服务器的连接,可能会导致资源消耗;
  • 缺乏高级功能:与专业的分布式消息队列系统相比,缺乏消息确认、持久化、事务、死信队列等高级功能。

总结

Redis的发布订阅模式是一种轻量级的消息传递机制,适用于需要快速、简单消息传递的场景。然而,对于需要高可靠性、高吞吐量和复杂消息处理能力的场景,可能需要考虑使用专业的分布式消息队列系统。

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

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

相关文章

惠中科技光伏清洗剂:绿色清洁,高效发电的守护者

在当今全球能源转型的大背景下&#xff0c;光伏产业作为绿色能源的重要组成部分&#xff0c;正以前所未有的速度蓬勃发展。然而&#xff0c;光伏板长期暴露于户外环境&#xff0c;不可避免地会遭受灰尘、鸟粪、油污等污染物的侵袭&#xff0c;这些污染物如同阴影般覆盖在光伏板…

代码随想录Day 35|动态规划,二维dp数组,滚动数组,leetcode题目:416.分割等和子集

提示&#xff1a;DDU&#xff0c;供自己复习使用。欢迎大家前来讨论~ 文章目录 动态规划Part03一、 动态规划&#xff1a;01背包理论基础01 背包二维dp数组01背包 二、动态规划&#xff1a;01背包理论基础&#xff08;滚动数组&#xff09;思路一维dp数组&#xff08;滚动数组&…

echarts三维立体扇形图+三维立体环形图配置详解记录

先看效果&#xff0c;注&#xff1a;三维立体echarts比较吃性能&#xff0c;同一页面如果有多个三维图进行渲染&#xff0c;进行跳转时可呢能会对整体页面产生影响&#xff0c;具体解决方法可查看本人另一篇文章 多个echarts使用3D导致页面卡顿的解决办法 三维立体扇形图 三维…

c# Avalonia 架构开发跨平台应用

实现了一个计算器的应用&#xff0c;先看在不同平台的效果 windows11上 ubuntu上 统信UOS 上 麒麟 kylin v10 好了&#xff0c;先说一下问题&#xff0c;如果想一套代码在不同平台同时运行&#xff0c;里面调用的逻辑还是要分系统的&#xff0c;先分linux系统和windows系统&a…

2024年全国铁路(铁路、高铁、地铁)矢量数据集

数据更新时间​&#xff1a;2024年6月​&#xff1b; ​数据范围&#xff1a;全国各省&#xff08;不包含台湾&#xff09;; 数据格式​&#xff1a;shp; ​数据包含类型&#xff1a;铁路、高铁、地铁 数据​坐标信息&#xff1a; EPSG Code 4326 大地基准面 D_WGS_1…

CTFSHOWRCE

web3 1.打开环境&#xff0c;上面给了一句php的话&#xff0c;意思是get传参url有文件包含漏洞 2.get传参运用伪协议&#xff0c;post传参命令执行看目录。 3.上面有一个文件ctf_go_go_go,访问这个文件就有flag web4 1.打开环境&#xff0c;和上一关的一样&#xff0c;但是不…

CSS实现优惠券透明圆形镂空打孔效果等能力学习

前言&#xff1a;无他&#xff0c;仅供学习记录&#xff0c;通过一个简单的优惠券Demo实践巩固CSS知识。 本次案例主要学习或巩固一下几点&#xff1a; 实现一个简单的Modal&#xff1b;如何进行复制文本到粘贴板&#xff1b;在不使用UI的svg图片的情况下&#xff0c;如何用C…

【C++】模板特化

目录 一、非类型模板参数 二、模板的特化 &#x1f31f;概念 扩展小知识补充(1)&#xff1a; 扩展小知识补充(2)&#xff1a; &#x1f31f;函数模板特化 扩展小知识&#xff1a; &#x1f31f;类模板特化 ✨全特化 ✨偏特化 • 部分特化&#xff1a;将模板参数表中…

前端几种常见框架【第一节】

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 最近比较忙&#xff0c;本人在复习软考中级设计考试&#xff0c;所以本系列文从零基础开始复习软考到结束软考&#xff08;计算机技术与软件专业技术资格考试&#xff09;作为国家级职业资格认证考试&#x…

ROS2 2D相机基于AprilTag实现3D空间定位最简流程

文章目录 前言驱动安装下载安装方式一&#xff1a;方式二&#xff1a; 相机检测配置config文件编译、运行程序注意 内参标定标定板运行程序 apriltag空间定位标签打印下载安装可视化结果 前言 AprilTag是一种高性能的视觉标记系统&#xff0c;广泛应用于机器人导航、增强现实和…

简述CCS平面线性光源

光源在机器视觉系统中起着重要作用&#xff0c;不同环境、场景及应用合适光源都不一样&#xff0c;今天我们来看看LFX3-PT系列平面线性光源。它是最适合检测镜面物体的凹凸,外壳小巧的光源。备有根据检测条件可选的2种线间距。1mm型&#xff08;型号末尾&#xff1a;A&#xff…

【ArcGIS Pro第一期】界面简介

ArcGIS Pro简介 ArcGIS Pro界面简介1.1 打开工程1.2 使用功能区上的工具 参考 ArcGIS Pro 是一种基于功能区的应用程序。 ArcGIS Pro 窗口顶部的功能区有许多命令可供选择&#xff0c;而根据需要打开的各个窗格&#xff08;可停靠窗口&#xff09;中则提供了更为高级或专用的功…

erlang学习:用ETS和DETS存储数据

作用 ets和dets是两个系统模块&#xff0c;可以用来高效存储海量的Erlang数据。 ETS和DETS执行的任务基本相同&#xff1a;它们提供大型的键值查询表。ETS常驻内存&#xff0c;DETS则常驻磁盘。ETS是相当高效的&#xff1a;可以用它存储海量的数据&#xff08;只要有足够的内…

ACM模式 输入输出练习

牛客-练习地址 第一题 let cnt readline(); while(cnt--){let input readline()let arr input.split( ).map(Number)console.log(arr[0]arr[1]) }第二题 let cnt readline(); while(cnt--){let input readline()let arr input.split( ).map(Number)console.log(arr[0]ar…

Web攻防之应急响应(二)

目录 前提 &#x1f354;学习Java内存马前置知识 内存马 内存马的介绍 内存马的类型众多 内存马的存在形式 Java web的基础知识&#xff1a; Java内存马的排查思路&#xff1a; &#x1f354;开始查杀之前的需要准备 1.登录主机启动服务器 2.生成jsp马并连接成功 …

vivado 创建时间约束1

步骤3&#xff1a;创建时间约束 在此步骤中&#xff0c;您打开合成的设计并使用AMD Vivado™定时约束 男巫定时约束向导分析门级网表并发现缺失 约束。使用“定时约束”向导为此设计生成约束。 1.在“流导航器”中&#xff0c;单击“打开综合设计”。 2.当综合设计打开时&#…

六、MySQL高级—架构介绍(1)

&#x1f33b;&#x1f33b; 目录 一、Mysql 简介1.1 概述1.2 Mysql 高手是怎样炼成的 二、Mysql Linux 版的安装2.1 mysql5.52.2 mysql5.7 三、Mysql 的用户与权限管理3.1 MySQL的用户管理3.2 权限管理3.3 通过工具远程访问 四、 Mysql的一些杂项配置(了解)五、 Mysql 逻辑架构…

[UVM]3.核心基类 uvm_object 域的自动化 copy() compare() print() pack unpack

1.核心基类&#xff1a;uvm_object &#xff08;1&#xff09;虚类只能声明&#xff0c;不能例化。 &#xff08;2&#xff09;uvm_object提供的方法 2.域的自动化&#xff08;field automation&#xff09; &#xff08;1&#xff09;简述 &#xff08;2&#xff09;示例 格…

JVM5-垃圾回收

自动垃圾回收 在C/C这类没有自动垃圾回收机制的语言中&#xff0c;一个对象如果不再使用&#xff0c;需要手动释放&#xff0c;否则就会出现内存泄漏&#xff0c;称这种释放对象的过程为垃圾回收&#xff0c;而需要程序员编写代码进行回收的方式为手动回收 内存泄漏指的是不再…

进一步了解CSS布局——WEB开发系列29

CSS 页面布局技术允许我们拾取网页中的元素&#xff0c;并且控制它们相对正常布局流、周边元素、父容器或者主视口/窗口的位置。 一、正常布局流&#xff08;Normal Flow&#xff09; CSS的布局基础是“正常流”&#xff0c;也就是页面元素在没有特别指定布局方式时的默认排列…