【Redis实战】有MQ为啥不用?用Redis作消息队列!?Redis作消息队列使用方法及底层原理高级进阶

 🎉🎉欢迎光临🎉🎉

🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀

🌟特别推荐给大家我的最新专栏《Redis实战与进阶》

本专栏纯属为爱发电永久免费!!!

这是苏泽的个人主页可以看到我其他的内容哦👇👇

努力的苏泽icon-default.png?t=N7T8http://suzee.blog.csdn.net/

最近工作室的一个业务跟另一个业务合并 自然要用到MQ(消息队列Message Queue)那么很显然 就要部署个RabbitMQ到服务器上了  

我们用的是云托管的的服务 那自然是部署中间件到云服务上去了 服务是一路开通 结果到了需要调试的时候 怎么也连不上 (说是内网直连,但关键是 同事们都在线下做本地测试的呀)

直接无语了 面对这一场景 怎么办?业务还要继续 等着交货的  于是我想起了之前学过的技术栈 

Redis 也能作为消息队列的(不过用的比较少所以不大容易记起来 或者也没啥人知道) 于是一顿卡卡操作  步骤还比MQ简单  下面就来看是如何实现的

正片

目录

最近工作室的一个业务跟另一个业务合并 自然要用到MQ(消息队列Message Queue)那么很显然 就要部署个RabbitMQ到服务器上了  

Redis 也能作为消息队列的(不过用的比较少所以不大容易记起来 或者也没啥人知道) 于是一顿卡卡操作  步骤还比MQ简单  下面就来看是如何实现的

正片

Redis作为消息队列的优缺点:

使用Redis作为消息队列的选择相对于使用专门的消息队列系统(如RabbitMQ、Kafka等)有以下优点和:

缺点也很明显:

应用场景:

Redis实现消息队列系统 实现步骤:

配置Redis:

设置RedisTemplate的序列化器。在消息队列中,你可以使用默认的序列化器,即StringRedisSerializer,它会将消息以字符串的形式进行存储和传输。可以通过以下代码设置默认的序列化器:

实现消息的发布和订阅功能。

实战与改良

代码解释

我把消息处理的系统中心化处理,也就是说是这个监听系统他可以监听reserved通道的所有业务类型,我这里列举了四种wait,agree,refuse,over四种 但如果是更大的业务体系 同一个通道可能面临着更多可能性分支  那如果按照第一套的方案 需要对每一个具体业务实现一个监听者 工作量就很大(可能这样耦合会低一些吧)

但是我这样把消息集中处理 然后短信发送系统就专门只做短信发送的事情 xx系统就只做对应的工作 就能把工作上的耦合度大大降低

那么大家应该也注意到我的两个负责序列化和反序列化的方法了吧 这是因为业务需求 要把对象封装成一个类 所以这里的方案就是在信息中心处理器上 自定义一个序列化方案(如果再做得好一点其实可以把这个序列器抽理出来封装为一个抽象方法 用泛型来定义返回结果和参数 这样就能序列化所有引用的类型了)  

遇到的问题:

对了 中途遇到了这样一个错误

原因与分析:

实际业务中的测试

发布服务

订阅服务(监听服务)

测试结果


Redis作为消息队列的优缺点:

使用Redis作为消息队列的选择相对于使用专门的消息队列系统(如RabbitMQ、Kafka等)有以下优点和:

  1. 简单轻量:Redis是一个内存中的数据存储系统,具有轻量级和简单的特点。相比较专门的消息队列系统,使用Redis作为消息队列不需要引入额外的组件和依赖,可以减少系统的复杂性。

  2. 速度快:由于Redis存储在内存中,它具有非常高的读写性能。这对于需要低延迟的应用程序非常有优势。

  3. 多种数据结构支持:Redis提供了丰富的数据结构,如列表、发布/订阅、有序集合等。这使得Redis在处理不同类型的消息和任务时更加灵活。

  4. 数据持久化:Redis可以通过将数据持久化到磁盘来提供数据的持久性。这意味着即使Redis重启,之前的消息也不会丢失。

  5. 广泛的应用场景:Redis不仅可以用作消息队列,还可以用作缓存、数据库、分布式锁等多种用途。如果你的应用程序已经使用了Redis,那么使用Redis作为消息队列可以减少技术栈的复杂性。

缺点也很明显:

  1. 缺少一些高级特性:相对于专门的消息队列系统,Redis在消息队列方面的功能可能相对简单。例如,它可能缺乏一些高级消息传递功能,如消息重试、消息路由、持久化消息等。

  2. 可靠性和一致性:Redis的主要设计目标是提供高性能和低延迟,而不是强一致性和高可靠性。在某些情况下,Redis可能会丢失消息,或者在出现故障时可能无法提供持久性保证。

应用场景:

适用于简单的中小型项目 如果功能简单,访问量并不大可以考虑
如果你的应用程序对可靠性和高级功能有严格要求,并且需要处理大量的消息和复杂的消息路由,那么使用专门的消息队列系统可能更合适。

Redis实现消息队列系统 实现步骤:

配置Redis:

  1. 首先,确保你已经正确地配置了Redis和Lettuce依赖项,并创建了LettuceConnectionFactory对象。

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
      redis:host: port: 6379password: lettuce:pool:max-active: 1000max-idle: 1000min-idle: 0time-between-eviction-runs: 10smax-wait: 10000

  2. 创建一个RedisTemplate对象,并将LettuceConnectionFactory设置为其连接工厂:

 @Beanpublic RedisTemplate<String, String> redisTemplate(LettuceConnectionFactory connectionFactory) {RedisTemplate<String, String> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setDefaultSerializer(new StringRedisSerializer());return template;}

设置RedisTemplate的序列化器。在消息队列中,你可以使用默认的序列化器,即StringRedisSerializer,它会将消息以字符串的形式进行存储和传输。可以通过以下代码设置默认的序列化器:

redisTemplate.setDefaultSerializer(new StringRedisSerializer());

实现消息的发布和订阅功能。

  • 发布消息:
    redisTemplate.convertAndSend("channel_name", "message_payload");

    在上述代码中,"channel_name"是消息的通道名称,"message_payload"是要发布的消息内容。

  • 订阅消息:
  • 首先,创建一个MessageListener实现类来处理接收到的消息:

public class MessageListenerImpl implements MessageListener {@Overridepublic void onMessage(Message message, byte[] pattern) {// 处理接收到的消息String channel = new String(message.getChannel());String payload = new String(message.getBody());// 执行自定义的逻辑}
}

创建一个LettuceMessageListenerAdapter对象,并提供MessageListener实现类:

LettuceMessageListenerAdapter listenerAdapter = new LettuceMessageListenerAdapter(new MessageListenerImpl());
listenerAdapter.afterPropertiesSet();

创建一个RedisMessageListenerContainer对象,并配置它的LettuceConnectionFactory和监听适配器:

RedisMessageListenerContainer listenerContainer = new RedisMessageListenerContainer();
listenerContainer.setConnectionFactory(lettuceConnectionFactory);
listenerContainer.addMessageListener(listenerAdapter, new ChannelTopic("通道名称"));
listenerContainer.start();

通过以上步骤,我们创建了一个LettuceConnectionFactory对象来与Redis服务器建立连接。然后,我们创建了一个MessageListener实现类来处理接收到的消息。接下来,我们创建了一个LettuceMessageListenerAdapter对象,并提供MessageListener实现类。最后,我们创建了一个RedisMessageListenerContainer对象,并配置它的LettuceConnectionFactory和监听适配器,然后启动容器以开始监听指定通道上的消息。

以上的方案 好处就是 可以很明显的知道监听者在哪个部分 监听对应通道的信息 然而 业务当中 如果每一个对应模块的业务和通道都建立一个监听者来进行监听(我们假设每一个就业务所要得到消息以后所执行的逻辑都不相同) 那这个工作量就会暴增 

于是就有了第二种写法 :

实战与改良

/**** @title MessageManager* @author SUZE* @Date 2-17**/
@Component
public class ReservedMessageManager {private String ListenerId;private String UserId;private String message;private final RedisTemplate<String, String> redisTemplate;@Autowiredpublic ReservedMessageManager(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;subscribeToChannel("reserved");}@Resourceprivate SmsServer smsServer;public void publishMessage(String channel, reserveMessage message) {String  Message=serialize(message);redisTemplate.convertAndSend("channel_name", "message_payload");redisTemplate.convertAndSend(channel, Message);}// 接收到消息时触发的事件private void handleReserveMessage(String channel, reserveMessage reserveMessage) {if (reserveMessage != null) {String userId = reserveMessage.getUserId();String ListenerId=reserveMessage.getListenerId();String message = reserveMessage.getMessage();//TODO 处理接收到的消息逻辑 这里后续要对Message进行一个检测他有wait agree和refused和over四种状态 思种状态就是不一样的发送内容switch (message){//TODO 消息要给两边都发 所以要发两份 发信息的文案case "wait":smsServer.sendSms(userId,ListenerId,message);break;case "agree":smsServer.sendSms(userId,ListenerId,message);break;case "refuse":smsServer.sendSms(userId,ListenerId,message);break;case "over"://这里要操作文档系统了//拒绝的话 那就要监听一下smsServer.sendSms(userId,ListenerId,message);break;}//smsServer.sendSms(userId,ListenerId,message);// 其他处理逻辑...}}public void subscribeToChannel(String channel) {redisTemplate.execute((RedisCallback<Object>) (connection) -> {connection.subscribe((message, pattern) -> {String channelName = new String(message.getChannel());byte[] body = message.getBody();// 解析接收到的消息switch (channelName){case "reserved":reserveMessage reserveMessage = deserializeMessage(new String(body));handleReserveMessage(channelName, reserveMessage);break;//还有其他的通道 例如refuse就是一个 拒绝通道 专门监听拒绝的理由}}, channel.getBytes());return null;});}// 反序列化private reserveMessage deserializeMessage(String body) {ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.readValue(body, reserveMessage.class);} catch (IOException e) {// 处理反序列化异常e.printStackTrace();return null;}}// 序列化public String serialize(reserveMessage reserveMessage) throws SerializationException {if (reserveMessage == null) {return null;}try {ObjectMapper objectMapper = new ObjectMapper();return objectMapper.writeValueAsString(reserveMessage);} catch (JsonProcessingException e) {throw new SerializationException("Error serializing object", e);}}}

代码解释

  1. subscribeToChannel方法接受一个channel参数,用于指定要订阅的通道名称。
  2. redisTemplate.execute方法用于执行Redis操作,并传入一个RedisCallback回调函数。
  3. 回调函数使用lambda表达式的形式实现,接受一个connection参数,表示与Redis的连接。
  4. 在回调函数中,调用connection.subscribe方法来订阅通道。该方法接受一个回调函数作为参数,用于处理接收到的消息。
  5. 在消息回调函数中,首先从message对象中获取通道名称和消息体。
  6. 使用new String(message.getChannel())将通道名称转换为字符串类型,并存储在channelName变量中。
  7. 使用message.getBody()获取消息体的字节数组表示,并存储在body变量中。
  8. switch语句中,根据通道名称进行不同的处理。在这个例子中,仅处理"reserved"通道。
  9. 对于"reserved"通道的处理,调用deserializeMessage方法将消息体反序列化为reserveMessage对象,并将其存储在名为reserveMessage的局部变量中。
  10. 调用handleReserveMessage方法,将通道名称和反序列化后的reserveMessage对象作为参数进行处理。
  11. handleReserveMessage方法用于处理接收到的保留消息的逻辑。它检查消息类型,并根据类型执行不同的操作。根据消息类型,它调用smsServer.sendSms方法向指定的userIdlistenerId发送短信。

我把消息处理的系统中心化处理,也就是说是这个监听系统他可以监听reserved通道的所有业务类型,我这里列举了四种wait,agree,refuse,over四种 但如果是更大的业务体系 同一个通道可能面临着更多可能性分支  那如果按照第一套的方案 需要对每一个具体业务实现一个监听者 工作量就很大(可能这样耦合会低一些吧)

但是我这样把消息集中处理 然后短信发送系统就专门只做短信发送的事情 xx系统就只做对应的工作 就能把工作上的耦合度大大降低

那么大家应该也注意到我的两个负责序列化和反序列化的方法了吧 这是因为业务需求 要把对象封装成一个类 所以这里的方案就是在信息中心处理器上 自定义一个序列化方案(如果再做得好一点其实可以把这个序列器抽理出来封装为一个抽象方法 用泛型来定义返回结果和参数 这样就能序列化所有引用的类型了)  

遇到的问题:


对了 中途遇到了这样一个错误

错误信息:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `TopOne.MessageSystem.entity.reserveMessage` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

原因与分析:

reserveMessage类缺少默认构造函数,这导致Jackson库无法构造该类的实例。错误消息中提到了以下内容:"Cannot construct instance of TopOne.MessageSystem.entity.reserveMessage (no Creators, like default constructor, exist)"。
为了使Jackson能够正确地反序列化对象,需要在reserveMessage类中添加一个默认构造函数。默认构造函数是一个无参数的构造函数,它不需要任何参数来创建对象。
在你的reserveMessage类中

这个是改好的封装类:
 

@Data
public class reserveMessage {private String UserId;private String ListenerId;private String message;public reserveMessage() {// 默认构造函数}public reserveMessage(String userId, String ListenerId,String message) {this.UserId = userId;this.ListenerId = ListenerId;this.message=message;}}

实际业务中的测试

发布服务

订阅服务(监听服务)

测试结果

成功

这里面的打印是代替了原本业务中的短信发送 也算是成了

这一期就到这 感谢观看

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

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

相关文章

致敬新春“不回家”的厨师,李锦记让厨师的年味更有滋味

“新春饭市万家团圆&#xff0c;致敬千万坚守岗位的厨师” 新春团圆饭向来是餐饮行业最为关注的节点&#xff0c;但过去几年&#xff0c;在疫情与后疫情时期&#xff0c;新年团圆饭市不免冷清。而今年餐饮行业真正迎来“龙抬头”&#xff0c;龙年除夕夜的团圆饭市终于重迎来了…

腾讯云4核8G服务器能支持多少人访问?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

挑战杯 Yolov安全帽佩戴检测 危险区域进入检测 - 深度学习 opencv

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; Yolov安全帽佩戴检测 危险区域进入检测 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&am…

Quantitative Analysis: PIM Chip Demands for LLAMA-7B inference

1 Architecture 如果将LLAMA-7B模型参数量化为4bit&#xff0c;则存储模型参数需要3.3GB。那么&#xff0c;至少PIM chip 的存储至少要4GB。 AiM单个bank为32MB&#xff0c;单个die 512MB&#xff0c;至少需要8个die的芯片。8个die集成在一个芯片上。 提供816bank级别的访存带…

C++入门学习(二十九)goto语句

在C中&#xff0c;goto语句是一种控制流语句&#xff0c;用于无条件地转移到程序中指定的行。goto语句的使用通常是不推荐的&#xff0c;因为它可能导致代码结构变得混乱、不易理解和维护。然而&#xff0c;在某些特殊情况下&#xff0c;goto语句可能是一种有效的解决方法。 示…

php switch、for、foreach、while、do...while

php switch 1. switch2. for循环3. foreach4. while、do...while 1. switch <?php$height 190;switch ($height) {case 160:echo 太矮了;break; //跳出本次循环case 170:echo 还行吧;break; //跳出本次循环case 180:echo 帅哥;break; //跳出本次循环default:echo 迷; }2.…

机器学习面试:请你谈谈生成模型和判别模型的区别?

生成模型:由数据学习联合概率密度分布P(XY)&#xff0c;然后求出条件概率分布P(YIX)作为预测的模型&#xff0c;即生成模型:P(Y|X) P(X,Y)/ P(X)(贝叶斯概率)。基本思想是首先建立样本的联合概率概率密度模型P(X,Y)然后再得到后验概率P(Y|X)&#xff0c;再利用它进行分类。典型…

智胜未来,新时代IT技术人风口攻略-第四版(弃稿)

文章目录 前言鸿蒙生态科普调研人员画像高校助力鸿蒙高校鸿蒙课程开设占比教研力量并非唯一原因 企业布局规划全盘接纳仍需一段时间企业对鸿蒙的一些诉求 机构入场红利机构鸿蒙课程开设占比机构对鸿蒙的一些诉求 鸿蒙实际体验高校用户群体高度认同与影响体验企业用户群体未来可…

[高并发] - 1.高并发综述

1. 必备条件 高并发&#xff0c;高性能分布式ID 高并发过滤组件 Bloom FIlter 2. 数据库 &#xff08;1&#xff09;不要让mysql干不擅长的工作&#xff0c;例如全文搜索&#xff0c;而是采用对应的nosql来处理&#xff1b;对于擅长的存取数据则能很好胜任&#xff1b; &am…

Linux进程间通信(三)-----System V消息队列

消息队列的概念及原理 消息队列实际上就是在系统当中创建了一个队列&#xff0c;队列当中的每个成员都是一个数据块&#xff0c;这些数据块都由类型和信息两部分构成&#xff0c;两个互相通信的进程通过某种方式看到同一个消息队列&#xff0c;这两个进程向对方发数据时&#x…

MySQL DQL 基本查询

一.概念 数据查询不应只是简单返回数据库中存储的数据&#xff0c;还应该根据需要对数据进行筛选以及确定数据以什么样的格式显示。 二.语法格式 select 列名 from 表 where 条件 1.查询所有的商品 select * from product; 2.查询商品名和商品价格 select pname,price from…

最新PyCharm安装详细教程及pycharm配置

目录 一、PyCharm简介及其下载网站 二、单击网站的Downloads&#xff0c;进入二级页面&#xff0c;选择对应的操作系统下载PyCharm 三、PyCharm的安装程序的安装及其配置(configuration) 1、运行PyCharm Setup 2、安装位置设置 3、安装选项设置 4、开始菜单中PyCharm快捷方式的…

【C语言】实现队列

目录 &#xff08;一&#xff09;队列 &#xff08;二&#xff09;头文件 &#xff08;三&#xff09; 功能实现 &#xff08;1&#xff09;初始化 &#xff08;2&#xff09; 销毁队列 &#xff08;3&#xff09; 入队 &#xff08;4&#xff09;出队 &#xff08;5&a…

centos7系列:出现ZooKeeper JMX enabled by default这种错误的解决方法

出现ZooKeeper JMX enabled by default这种错误的解决方法 前言一 问题描述二 解决方法2.1 可能的原因分析2.2 小编的问题解决方法First&#xff1a;检查/etc/profile里面zookeeper的环境变量配置Second&#xff1a;检查 zookeeper/conf/zoo.cfg里面的dataDir的路径 总结 前言 …

【Linux内核】从0开始入门Linux Kernel源码

&#x1f308; 博客个人主页&#xff1a;Chris在Coding &#x1f3a5; 本文所属专栏&#xff1a;[Linux内核] ❤️ 前置学习专栏&#xff1a;[Linux学习]从0到1 ⏰ 我们仍在旅途 ​ 目录 …

Midjourney绘图欣赏系列(五)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子&#xff0c;它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同&#xff0c;Midjourney 是自筹资金且闭源的&#xff0c;因此确切了解其幕后内容尚不…

MATLAB知识点:nchoosek函数(★★★☆☆)用来计算组合数,也能返回从向量v中抽取k个元素的所有组合

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章&#xff1a;课后习题讲解中拓展的函数 在讲解第三…

模型 4E(交换、体验、随处、传教)理论

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。重在提升认知。聚焦体验营销。 1 模型 4E(交换、体验、随处、传教)理论的应用 1.1 4E 理论在软件产品营销中的应用 某软件公司利用 4E 理论提升软件产品的市场占有率。具体如下&#xff1a; Exchange&a…

人工智能学习与实训笔记(四):神经网络之NLP基础—词向量

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 四、自然语言处理 4.1 词向量 (Word Embedding) 4.1.1 词向量的生成过程 4.1.2 word2vec介绍 4.1.3 word2vec&#xff1a;skip-gram算法的实现 4.2 句向量 - 情感分析 4.2.1 LSTM (Long S…

C语言之日历问题

一、代码展示 #include<stdio.h> int leapyear(int year)//判断是不是闰年函数 {if (year % 4 0 && year % 100 ! 0 || year % 400 0)return 1;elsereturn 0; } int days(int year, int month, int* day)//判断一个月有几天 {if (month ! 2)return day[month…