Redis实现发布/订阅功能(实战篇)

news2024/12/23 8:44:35

前言

博主在学习 Redis 实现发布订阅功能的时候,踩了太多的坑。

不是讲解不详细,看的一知半解;就是代码有问题,实际压根跑不起来!

于是博主萌生了自己写一个最新版且全程无错的博客供各位参考。希望各位不要把我才过的坑再踩一遍。(实战篇的所有代码均由本人测试,全程无Bug。)

废话不多说,让我们进入实战篇的学习!

在开始实战篇的之前,我们先一起回顾下原理篇的内容。

Redis 发布/订阅的优点

  • 高性能:Redis 作为内存存储,具备极高的读写性能,能够快速处理发布和订阅消息。
  • 简单易用:Redis 的发布/订阅接口简单,易于集成和使用。
  • 实时性强:发布的消息会立即传递给所有订阅者,具备高实时性。

Redis 发布/订阅的缺点

  • 消息丢失:由于 Redis 是内存存储,如果 Redis 实例宕机,未处理的消息可能会丢失。
  • 无法持久化:Redis 的发布/订阅模式不支持消息持久化,无法存储和检索历史消息。
  • 订阅者不可控:发布者无法控制订阅者的数量和状态,无法保证所有订阅者都能接收到消息。
  • 无确认机制:发布者无法确认消息是否被订阅者接收和处理。

正如上述中Redis的缺点,Redis的发布订阅功能并不可靠,如果我们需要保证消息的可靠性、包括确认、重试等要求,我们还是要选择使用MQ实现发布订阅。

Redis的发布/订阅应用场景:

  • 对于消息处理可靠性要求不强
  • 消息无需持久化
  • 消费能力无需通过增加消费方进行增强
  • 架构简单 中小型系统不希望应用过多中间件

Redis发布订阅命令

命令

描述

Redis Unsubscribe 命令

指退订给定的频道

Redis Subscribe 命令

订阅给定的一个或多个频道的信息

Redis Pubsub 命令

查看订阅与发布系统状态

Redis Punsubscribe 命令

退订所有给定模式的频道

Redis Publish 命令

将信息发送到指定的频道

Redis Psubscribe 命令

订阅一个或多个符合给定模式的频道


正式开始

一、添加依赖

首先,确保你已经安装并配置好 Redis 服务器,并创建了 Spring Boot 项目,在pom.xml中引入依赖。

<!-- 所需依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

二、配置文件中配置Redis

spring:
  # 项目名称
  application:
    name: test-redis-boot

  # Redis 配置
  data:
    redis:
      host: 填写自己的主机IP
      port: 8000
      password: 有则填,没有去掉这个属性
      database: 1
      # 连接超时时间
      timeout: 10s
      lettuce:
        pool:
          # 连接池中的最小空闲连接
          min-idle: 5
          # 连接池中的最大空闲连接
          max-idle: 8
          # 连接池的最大数据库连接数
          max-active: 20
          # #连接池最大阻塞等待时间(使用负值表示没有限制)
          max-wait: -1ms

三、创建Redis配置类

创建一个配置类,用于配置 Redis连接工厂和消息监听器监听通道信息

注:此配置类无需死记硬背。只需大致了解每个方法的作用即可。

/**
 * @Description Redis 配置类,用于配置 Redis 连接工厂和消息监听器监听通道信息
 * @Author gongming.Zhang
 * @Date 2024/9/11 18:27
 * @Version 1.0
 */
@Configuration
@Slf4j
public class RedisConfig {

    /**
     * 自定义 RedisTemplate 序列化方式
     *
     * @param redisConnectionFactory Redis 连接的线程安全工厂
     * @return 模板类
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        // 绑定 RedisConnectionFactory
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 创建 Jackson2JsonRedisSerializer 序列方式,对象类型使用 Object 类型。
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(new LaissezFaireSubTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(objectMapper, Object.class);

        // 设置 RedisTemplate 序列化规则,因为 key 通常是普通的字符串,所以使用StringRedisSerializer即可,而 value 是对象时,才需要使用序列化与反序列化。
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash key 序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 属性设置后操作
        redisTemplate.afterPropertiesSet();
        log.info("RedisTemplate 自定义序列化配置完毕...");
        return redisTemplate;
    }

    /**
     * 配置主题订阅
     * 
     * 可以添加多个监听器,监听多个通道,只需要将消息监听器与订阅的通道/主题绑定即可。
     * addMessageListener(MessageListener listener, Collection<? extends Topic> topics):将消息监听器与多个订阅的通道/主题绑定
     * addMessageListener(MessageListener listener, Topic topic):将消息监听器与订阅的通道/主题绑定
     *
     * @param connectionFactory Redis 连接的线程安全工厂
     * @return 容器对象
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                   MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 设置连接工厂,RedisConnectionFactory 可以直接从容器中取,也可以从 RedisTemplate 中取
        container.setConnectionFactory(connectionFactory);
        // 订阅名称叫 cache 的通道, 类似 Redis 中的 subscribe 命令
        container.addMessageListener(listenerAdapter, new ChannelTopic("cache"));
        // 订阅名称以 'test-' 开头的全部通道, 类似 Redis 的 pSubscribe 命令
        container.addMessageListener(listenerAdapter, new PatternTopic("test-*"));
        log.info("消息监听器和通道绑定完毕...");
        return container;
    }

    /**
     * 配置消息监听适配器
     *
     * @param redisReceiveListener
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter(RedisReceiveListener redisReceiveListener) {
        return new MessageListenerAdapter(redisReceiveListener);
    }
}

四、创建消息发送服务端

创建一个消息发布类,用于向客户端发布消息。

/**
 * @Description 消息发布服务端
 * @Author gongming.Zhang
 * @Date 2024/9/12 9:42
 * @Version 1.0
 */
@Component
@Slf4j
public class MessagePublisher {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 服务端发布消息
     *
     * @param channel 通道名
     * @param message 待发送的消息
     */
    public void sendMessage(String channel, String message) {
        redisTemplate.convertAndSend(channel, message);
        log.info("消息发送成功...  channel={}, message={}", channel, message);
    }

}

五、创建监听器(客户端)

用于监听服务端发送的消息,每次服务端发送新消息时,都会自动调用onMessage()方法。

/**
 * @Description Redis 消息监听器
 * @Author gongming.Zhang
 * @Date 2024/9/11 18:53
 * @Version 1.0
 */
/*
    当收到订阅的消息时,会将消息交给这个类处理。
     * 可以直接实现 MessageListener 接口,也可以继承它的实现类 MessageListenerAdapter.
     * 自动多线程处理,打印日志即可看出,即使手动延迟,也不会影响后面消息的接收。
 */
@Component
@Slf4j
public class RedisReceiveListener implements MessageListener {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 处理回调逻辑。每次新消息到达时,都会调用此方法。通过 onMessage 方法执行业务代码
     * <p>
     * 该接口不仅可以访问实际消息,还可以访问接收消息的频道(Channel),以及订阅时用于匹配频道(Channel)的模式。
     * 此信息使被调用者不仅可以通过内容区分各种消息,还可以检查其他详细信息。
     *
     * @param message 消息对象,不能为 null
     * @param pattern 与通道匹配的模式(如果指定),可以为 null
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 1.获取消息所属的通道  -->  首先获取 字符串序列化器,再从给定的二进制数据中反序列化对象。
        String channel = redisTemplate.getStringSerializer().deserialize(message.getChannel());
        // 2.获取客户端发送的消息内容  -->  后期可以根据自己项目中 消息 的类型,来确定用什么序列化器
        Object msg = redisTemplate.getValueSerializer().deserialize(message.getBody());
        log.info("收到Redis订阅消息: channel={} msg={}", channel, msg);
    }
}

六、编写Controller测试

/**
 * @Description 测试订阅发布功能
 * @Author gongming.Zhang
 * @Date 2024/9/12 10:13
 * @Version 1.0
 */
@RestController
@RequestMapping(value = "/api/v1/publish")
public class PublisherController {

    @Autowired
    private MessagePublisher publisher;

    @GetMapping("/sendMessage")
    public String sendMessage(@RequestParam(value = "message") String message, @RequestParam(value = "channel") String channel) {
        publisher.sendMessage(channel, message);
        return "Message published: " + message;
    }
}

七、测试响应数据

至此,我们 SpringBoot 整合 Redis 实现发布订阅功能已经完成!


总结

通过本文,我们详细介绍了如何在 SpringBoot 中整合 Redis 实现发布/订阅功能,并提供了详细的代码示例。Redis 发布/订阅模式以其高性能和简单易用的特点,在实时消息传递场景中有着广泛的应用,但同时也需要注意其消息丢失和无法持久化等缺点,需要根据实际业务需求选择。

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

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

相关文章

C语言浮点型数据在内存中的存储(23)

文章目录 前言一、浮点数在内存中的存储练习引入浮点数的存储浮点数存的过程 二、浮点数取的过程E不全为0或不全为1E全为0E全为1 三、再回顾练习总结 前言 哎&#xff0c;之前写了一篇&#xff0c;可是中途退出没保存&#xff0c;只能再写一遍了~   浮点数在内存中的存储跟整…

智汇创想pytest接口自动化测试框架

本测试框架是基于pytest搭建的接口自动化框架&#xff0c;对象为深圳智汇创想官方网站。深圳智汇创想科技有限责任公司&#xff08;深圳智汇创想科技有限责任公司&#xff09;&#xff0c;是一家专注于跨境电子商务的集团公司&#xff0c;全球电商平台多品类多品牌的零售商&…

什么是APT攻击,有哪些防御策略

在数字化时代&#xff0c;网络安全已成为国家、企业和个人不可忽视的重要议题。其中&#xff0c;高级持续性威胁&#xff08;APT&#xff09;攻击以其隐蔽性强、攻击规模大、持续时间长等特点&#xff0c;成为网络安全领域最为棘手的问题之一。面对APT攻击的严峻挑战&#xff0…

Unity 场景优化(1) game视口的Statistics 内容介绍

Unity的 Statistics &#xff08;stats&#xff09; Unity是多线程的。但是控制使用unity的api必须在主线程中&#xff0c;比如控制物体的transform信息。 Audio Level&#xff1a; DSP Load&#xff1a;数字信号处理&#xff08;Digital Signal Processing&#xff09;负载&…

空间视频化趋势理解

「视频空间化」这个趋势不是从现在开始&#xff0c;而是潜在发展了很多年了&#xff0c;而且我个人觉得「视频空间化」的背后其实对应的是「空间视频化」的趋势&#xff0c;所以未来我们还是要注重自己的技术栈中对视频相关处理技术的吸收以及整合&#xff0c;下面是我的几个理…

Jenkins生成html报告

下载插件 1.需要下载插件 html Publisher plugins 2.下载Groovy(设置css样式&#xff09;&#xff0c;默认没有css样式 在Job配置页面&#xff0c;增加构建步骤Execute system Groovy script&#xff0c;在Groovy Command中输入上面命令&#xff0c;即可&#xff1a; System.…

清理C盘缓存的垃圾,专业清理C盘缓存垃圾与优化运行内存的策略

专业清理C盘缓存垃圾与优化运行内存的策略 一、清理C盘缓存垃圾 在Windows操作系统中&#xff0c;C盘通常作为系统盘&#xff0c;其健康状况直接影响到系统的整体性能。定期清理C盘中的缓存和垃圾文件是维护系统性能的重要步骤。以下是一些专业建议&#xff1a; 1.使用磁盘清…

二叉树链式结构与简单实现

二叉树链式结构与简单实现 一、二叉树的链式结构二、二叉树的简单实现二叉树的遍历前序、中序以及后序遍历层序遍历 结点个数以及高度等二叉树的创建和销毁判断二叉树是否为完全二叉树 三、源码展示在 BinaryTree.h 中&#xff1a;在 BinaryTree.c 中&#xff1a; 以下代码环境…

建筑机器人通用操作系统设计方案

建筑机器人操作系统通用发行版概述 1. 基础版&#xff08;Entry Level&#xff09; 目标用户&#xff1a;小型建筑公司、DIY爱好者或初学者。特点&#xff1a;提供基础的机器人控制和任务管理功能&#xff0c;支持简单的自动化作业流程&#xff0c;如基础的混凝土搅拌、物料搬…

微信批量自动添加好友

现代社交领域中&#xff0c;微信已然成为人们生活中不可或缺的一部分。它不仅是朋友之间保持联系、分享生活点滴的重要沟通工具&#xff0c;更是商务人士拓展人脉、开展业务的得力助手。 在日常生活中&#xff0c;我们每天都会结识许多新朋友&#xff0c;这无疑为我们的社交圈…

两段有趣的代码(C语言函数指针)

目录 part1part2 两段有趣的代码 part1 (*(void (*)())0)();我们知道函数指针&#xff1a; void (*p)()去掉函数指针变量名就是函数指针的类型&#xff1a; void (*)()那这段代码我们就可以理解为将0强制转换为函数指针类型&#xff0c;再进行解引用;进行调用函数&#xff…

python容器四之字典

文章目录 1. 字典介绍2. 使用字典3. 字典的常见操作3.1 添加元素3.2 删除元素3.3 修改元素3.4 查找元素 4. 字典遍历方法4.1 遍历字典元素 5. 公共运算符6. 公共方法 1. 字典介绍 先来看看现实生活中的字典。我们知道&#xff0c;可以应用字典来查找汉字。 在这里插入图片描述…

Hash 专题

一、散列表 根据 key 计算 key 在表中的位置的数据结构&#xff1b;是 key 和其所在 存储地址的映射关系&#xff1b; 注意&#xff1a;散列表的节点中 kv 是存储在一起的&#xff1b; 结构&#xff1a; 二、选择hash的原因 1、需要很强的查找效率 2、强随机分布&#xff…

计算机网络:概述 - 计算机网络概述

目录 一. 互联网概述 1.1 网络 1.2 互联网 1.3 因特网 二. 互联网发展的三个阶段 三. 互联网的标准化工作 四. 互联网的组成 五. 计算机网络的类别 5.1 计算机网络的定义 5.2 计算机网络的不同类别 一. 互联网概述 起源于美国的互联网现如今已…

前端必知必会-响应式网页设计之媒体查询

文章目录 响应式网页设计 - 媒体查询什么是媒体查询&#xff1f;添加断点始终以移动设备为先进行设计另一个断点典型的设备断点方向&#xff1a;纵向/横向使用媒体查询隐藏元素使用媒体查询更改字体大小 总结 响应式网页设计 - 媒体查询 什么是媒体查询&#xff1f; 媒体查询…

动态规划前---选----

前言&#xff1a;基本的算法思路还是先看数据范围&#xff0c;接着看能不能用动态规划来做&#xff0c;刚刚好这个题目可以套用前—选— 题目地址 #include<bits/stdc.h> using namespace std;#define int long long int n,m; const int N (int)305; int dp[N][N]; // 前…

中国电子学会202406青少年软件编程(Python)等级考试试卷(四级)真题与解析

一、单选题(共25题, 共50分) 1.执行以下程序后所输出的结果是?( ) A 20 B 41 C 21 D 91 2.以下说法错误的是?( ) A python中可以在不同的自定义函数中声明相同名字的变量,使用时不会造成数据混乱 B 形参在其自定义函数内不能与其它变量重名 C …

初一信息科技2024指南辅助教学软件(抓包软件)

专门针对信息科技20204指南写的程序&#xff0c;互联网和直播等知识中包含tcp/ip和udp&#xff0c;三次握手等原理&#xff0c;需要简单明了的实验来说明&#xff0c;在机房中需要用抓包软件&#xff0c;可能需要安装windump npcap等软件非常繁琐&#xff0c;还需要接触保护卡&…

Leetcode 寻找重复数

可以使用 位运算 来解决这道题目。使用位运算的一个核心思想是基于数字的二进制表示&#xff0c;统计每一位上 1 的出现次数&#xff0c;并与期望的出现次数做比较。通过这种方法&#xff0c;可以推断出哪个数字重复。 class Solution { public:int findDuplicate(vector<i…

如何写出高效的软件测试用例?

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 编写测试用例的目的就是确保测试过程全面高效、有据可查。但要编写出高效的测试用例&#xff0c;需要搞清楚什么是测试用例&#xff0c;以及如何编写出高效的测试用…