黑马商城项目—最新SpringCloud开发实战—功能实现详细学习笔记(RabbitMQ篇)

2024最新版的SpringCloud黑马商城项目
笔记对应教学视频讲解序号,并附上每小节所在的视频分p位置
笔记包含了视频讲解的核心内容及实战功能实现的详细过程

课程地址: 2024最新SpringCloud微服务开发与实战,java黑马商城项目微服务实战开发(涵盖MybatisPlus、Docker、MQ、ES、Redis高级等)
项目代码:课程地址简介中领取

系列文章目录
本笔记包含Docker、微服务、RabbitMQ、Elasticsearch等(持续更新)

黑马商城项目—最新SpringCloud开发实战—功能实现详细学习笔记(Docker篇)
黑马商城项目—最新SpringCloud开发实战—功能实现详细学习笔记(微服务篇)
黑马商城项目—最新SpringCloud开发实战—功能实现详细学习笔记(RabbitMQ篇)
黑马商城项目—最新SpringCloud开发实战—功能实现详细学习笔记(Elasticsearch篇)


RabbitMQ

p85 MQ入门-01 MQ课程介绍

在这里插入图片描述
在这里插入图片描述
它是应用广泛的高性能异步通讯组件
在这里插入图片描述
使用MQ前,顺序执行操作
在这里插入图片描述
使用MQ后,异步执行操作,效率提高
在这里插入图片描述
基础篇和高级篇的学习内容

p86 MQ入门-02 初识MQ-同步调用优缺点

在这里插入图片描述
在这里插入图片描述
以余额支付为例,它首先扣减用户余额,然后更新支付状态,最后更新订单状态等,
其中扣减余额和更新支付状态为整个支付的核心业务,其它的是非核心业务,
这些非核心业务应该异步执行

同步调用的缺点:
1,拓展性差:如果后续有新的需求,那么调用会去调用很多服务,代码会变得很多,
2,性能下降:当依次调用的其中某个服务响应慢,会影响整个业务的响应时间,
3,级联失败:除了性能下降外,调用者会等待被调用者,会无效地占用调用者的资源,导致调用者雪崩,
如果调用的其中某个服务宕机了,还会导致数据不一致的情况
在这里插入图片描述
同步调用的优点是时效性强,等待结果后返回

p87 MQ入门-03 异步调用优缺点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
支付服务发送消息通知后,服务会去监听消息代理的消息:
有新的需求增加时,支付服务无需增加代码,解除了服务之间的耦合,拓展性也提高了,
无需等待非核心业务执行后返回,性能提升了,
对于宕机的服务,当服务重新上线后,消息代理继续通知未完成的消息,实现了故障隔离,
消息代理可以缓存突然激增的消息之后再慢慢处理,流量削峰
在这里插入图片描述

p88 MQ入门-04 初识MQ-技术选型

在这里插入图片描述
MQ常见的技术
RabbitMQ采用Erlang语言开发(这是一种面向并发的语言)
RabbitMQ支持集群,因此可用性高
RabbitMQ的单机吞吐量每秒10万,而Kafka的吞吐量每秒100万
目前市面上很少(小于10%)有公司的并发超过10万,而大多数的并发在几百
RabbitMQ的底层是基于内存的处理,它的消息延迟很低
如果消息能满足至少被消费1次,那么它的可靠性就高,RabbitMQ的可靠性比Kafka好,Kafka容易消息丢失
在这里插入图片描述
比较突出的技术是RabbitMQ和RoketMQ因为可靠性好,而Kafka适用于大数据对吞吐量有要求的

p89 MQ入门-05 RabbitMQ-安装部署

在这里插入图片描述
在这里插入图片描述
rabbitmq的官方文档
教学视频以rabbitmq版本3.8为例
在这里插入图片描述
采用docker来安装rabbitmq
它会提供一个控制台,然后需要设置用户名密码,挂载数据卷,创建网络(加入黑马商城项目的网络)
控制台端口为15672,收发消息的端口为5672
在这里插入图片描述
下载或者从tar包加载好镜像
在这里插入图片描述
Docker run创建容器
在这里插入图片描述
Docker logs -f mq查看日志,两个端口都打开了
在这里插入图片描述
访问rabbitmq的网页控制台,输入设置的用户名和密码
在这里插入图片描述
可以在首页看到总览信息,它的版本,消息队列当前状态,节点等
在这里插入图片描述
可以在首页看到总览信息,它的版本,消息队列当前状态,节点等
在这里插入图片描述
connections可以看到连接的消息发送者和接收者
channels表示收发消息的通道,用通道进行消息传输
在这里插入图片描述
这是一些默认的交换机,
exchanges交换机和queues队列用于管理消息
在这里插入图片描述
在企业中,部署的一个rabbitmq可能会由多个项目使用,
为了防止不同的项目创建的exchange冲突,需要做隔离,用virtual-host虚拟主机来完成(一个项目一个虚拟主机)
在这里插入图片描述
在控制台右上角可以看到此时是使用默认的虚拟主机

p90 MQ入门-06 RabbitMQ-快速入门

在这里插入图片描述
在这里插入图片描述
在控制台中进行rabbitmq测试
在这里插入图片描述
添加一个队列

测试向交换机发消息看队列是否能收到
在这里插入图片描述
进入默认的fanout交换机,发送一条消息
在这里插入图片描述
点击发送消息后,提示not routed,然后队列里面也没有收到,
说明消息丢失了(交换机没有存储消息的能力,交换机只负责转发消息)
队列没有收到消息的原因是交换机和队列没有建立一种关系
在这里插入图片描述
进入到交换机里面,点击bindings绑定,设置与它绑定的队列
在这里插入图片描述
进入到队列里面也能看到与它绑定的交换机
在这里插入图片描述
再次发送一条消息,没有刚才那个提示了
在这里插入图片描述
去队列里面查看,这2个队列都收到了消息
在这里插入图片描述
查看队列里面的消息,内容就是刚刚发送的内容
在这里插入图片描述

p91 MQ入门-07 RabbitMQ-数据隔离

在这里插入图片描述
在这里插入图片描述
希望不同的项目是隔离开的,通常的做法是一个项目对应一个用户,再为这个用户创建一个虚拟主机
在这里插入图片描述
Users是用户管理,可以看到创建的用户
在这里插入图片描述
Virtual Hosts可以看到创建的虚拟主机,斜杠是目前默认的虚拟主机
在这里插入图片描述
创建一个用户,tags设为管理员
在这里插入图片描述
可以看到刚刚创建的用户没有可以访问的虚拟主机
在这里插入图片描述
因此,使用这个用户登录后无法查看其它虚拟主机里面消息队列的内容
在这里插入图片描述
用hmall的身份登录后创建一个名为/hmall的虚拟主机,
在这里插入图片描述
创建完成后它默认当前用户可以使用,在Users中也可以看到
在这里插入图片描述
在交换机里面也出现了一堆默认的交换机,名称与之前的一样,但是它们属于新的虚拟主机/hmall

p92 MQ入门-08 Java客户端-快速入门

在这里插入图片描述
AMQP是rabbitmq采用的协议,该协议与语言平台无关
在这里插入图片描述
这里java客户端的演示不用官方的AMQP客户端,而是Spring AMQP客户端(在Spring官网里面可以找到)
在这里插入图片描述
打开课程资料的项目工程
在这里插入图片描述
可以看到有消息的发送者和接收者
在这里插入图片描述
这里的演示省略发送消息发到交换机,再发到队列的过程

在这里插入图片描述
在RabbitMQ控制台中创建队列
在这里插入图片描述
父工程里面引入amqp依赖,包含了rabbitmq
在这里插入图片描述
配置rabbitmq服务端信息
在这里插入图片描述
alt加enter快捷键创建一个单元测试 (单元测试要在启动类的包下或子包下)
在这里插入图片描述
在这里插入图片描述
发送一条消息,可以看到连接消息队列成功
在这里插入图片描述
队列里面出现了一条消息
在这里插入图片描述
然后在控制台里面可以看到了发送的消息内容
在这里插入图片描述
在这里插入图片描述
在消费者主类下创建一个类用于监听队列中的消息,这里接收的参数类型是String因为发送的消息类型也是String
在这里插入图片描述
启动消费者的主类,控制台输出一条日志,监听到了队列的消息
在这里插入图片描述

p93 MQ入门-09 Java客户端-WorkQueue

在这里插入图片描述
WorkQueue是消息发送模型,它让多个消费者共同消费队列中的消息
在这里插入图片描述
在这里插入图片描述
在接收者中定义2个消息监听者
(在实际生产中一般不会写成这样的2个方法,因为这2个消费者运行时消耗的是相同的机器资源,这样做没有意义,
因此,通常是写一个消费者方法,然后再部署多个实例分别在不同的机器上,它们代码相同,这些实例就是多个消费者)
在这里插入图片描述
在发送者中定义一个测试方法,发送50条消息,每条消息带上一个序号便于之后观察
在这里插入图片描述
启动消息接收者主程序,启动消息发送者的测试类,
控制台的部分打印输出,发现消费者2接收了奇数序号的消息,消费者1接收了偶数序号的消息,消息被交替消费了
可以发现:
消息只会被1个消费者处理(被处理了就不能被其它消费者处理)
消息会被消费者均匀分配(轮询)

多个消费者去消费一个队列可以加快消息的处理速度,因此,消息堆积的问题可以添加更多的消费者来解决

由于消费者实例运行的机器性能可能存在差异,需要设置消费者的处理速度来适配对应的机器性能
在这里插入图片描述
比如,设置消费者的处理速度为以上速度
在这里插入图片描述
设置的方式是让这个线程休眠
在这里插入图片描述
再次发送消息进行测试,发现处理了5秒才完成,预计是1秒多(消费者1的40与消费者2的5,那么每秒就是45条),
这里消费者1处理速度很快,只处理自己的那一半消息,处理完后就没有消息了,
消费者2的处理速度很慢,但依然要处理自己的那一半消息,造成了时间浪费(应该消费者1要多处理一些才行)
造成的原因是,rabbitmq默认是将消息轮询投递给消费者,不考虑消费者的处理速度
解决方法是修改rabbitmq的配置,将prefetch设为1,表示每次只能获取1条消息,处理完成才会获取下一个消息
在这里插入图片描述
将消费者的配置prefetch设为1
在这里插入图片描述

再次发送消息进行测试,发现消费者1不仅处理奇数序号的消息还处理了偶数序号的消息(消费者1处理了比一半更多的消息),
整个消息也在大约1秒内处理完毕,与预期相符(能者多劳)
在这里插入图片描述

p94 MQ入门-10 Java客户端-Fanout交换机

在这里插入图片描述
消息队列的完整模型包括:发送者、交换机、队列、消费者
在这里插入图片描述
交换机的类型有:fanout(广播)、direct(定向)、topic(话题)
在这里插入图片描述
fanout交换机会复制消息给多个绑定的队列,那么一个消息会被多个消费者消费
(这种交换机就可以在需要发送消息给多个微服务的情况下使用)
在这里插入图片描述
在这里插入图片描述
声明一个fanout交换机
在这里插入图片描述
将交换机与队列绑定
在这里插入图片描述
这里依然用convertAndSend方法,但是填入3个参数,
两个参数的方法会将第1个参数当成队列名,三个参数的方法会将第1个参数当成交换机名,
这里选三个参数的方法,由于现在是发送者只发到交换机因此第二个参数不填,routingKey参数是其它交换机会用到的
在这里插入图片描述
从控制台可以看到2个消费者都收到了消息

p95 MQ入门-11 Java客户端-Direct交换机

在这里插入图片描述希望消息被部分消费者收到,而不是所有的消费者
在这里插入图片描述

通过队列与交换机设置BingdingKey和发送者设置的RoutingKey,来决定哪些消费者能收到消息,
Direct交换机也能实现Fanout交换机的功能
(应用场景举例,比如用户取消了订单,只需要给交易服务发送消息,不用给用户发短信以及加积分了,即不用发送给所有的消费者)
在这里插入图片描述
设置交换机的绑定队列的RoutingKey
在这里插入图片描述
设置消费者监听消息
在这里插入图片描述
设置消息发送者的routingKey
在这里插入图片描述
两个消费者都监听到了消息
在这里插入图片描述
将routingKey设为blue
在这里插入图片描述
只有消费者1收到了消息
在这里插入图片描述

p96 MQ入门-12 Java客户端-Topic交换机

在这里插入图片描述
在这里插入图片描述
RoutingKey由多个单词组成,之间以点分割,允许使用通配符(井号和星号),
因此,具有共同前缀的routingKey可视为一个Topic,这种方式的拓展性更强
在这里插入图片描述
在这里插入图片描述
创建topic交换机
在这里插入图片描述
设置交换机绑定的队列并指定RoutingKey
在这里插入图片描述
2个消费者都监听到了
在这里插入图片描述
修改发送的RoutingKey,这次只有消费者1能收到
在这里插入图片描述

p97 MQ入门-13 Java客户端-基于Bean声明队列和交换机

在这里插入图片描述
基于Bean声明队列和交换机
在这里插入图片描述
在这里插入图片描述
这里声明交换机和队列有两种方式:new的方式、使用Buider的方式。这两种方式没有区别
其中,durable意味持久化
在这里插入图片描述
声明以及绑定的代码通常是在消费者里面写(而不是发送者),
这里声明1个交换机和2个队列,并将它们绑定,运行消费者主程序
在这里插入图片描述
进入RabbitMQ的控制台可以看到声明成功,并且完成了绑定

p98 MQ入门-14 Java客户端-基于注解声明队列和交换机

在这里插入图片描述
基于注解声明队列和交换机
在这里插入图片描述
之前的案例,这次在Java中声明(而不是控制台)

在这里插入图片描述
基于Bean的方式的缺点,如果要指定routingKey,但是一个Bean只能传一个参数,
有多个参数就要写多个Bean来实现绑定,这会造成Bean的数量很多,这个问题可以用注解解决

在这里插入图片描述
用一个注解完成队列声明、交换机声明、绑定关系
在这里插入图片描述

在监听者的注解中声明交换机和队列并且完成绑定
@RabbitListener注解的一个属性是bindings,而这个属性的值也是一个@QueueBinding注解
在这里插入图片描述
进入控制台,声明和绑定都正常配置了

p99 MQ入门-15 Java客户端-消息转换器

在这里插入图片描述
rabbitmq支持发送任意类型的对象,因此在发送消息时需要将java对象转换为字节,这个转换由消息转换器来完成
在这里插入图片描述
在这里插入图片描述
发送一条Map类型的消息,但不设置消费者,然后去队列里面查看这条消息
在这里插入图片描述
可以看到这条消息的内容是字符串
在这里插入图片描述
Spring rabbitmq的底层采用了jdk提供的序列化方式(ObjectOutputstream)将java对象转换成字节
在这里插入图片描述
jdk的序列化存在一些问题:反序列化时存在漏洞容易被注入代码、消息太大(比如刚刚很小的对象存储了170多个字节)、可读性差
在这里插入图片描述
建议采用JSON序列化
由于publisher和consumer都需要依赖,可以在父工程引入依赖
在这里插入图片描述
子工程引入了依赖
在这里插入图片描述
定义一个MessageConverter的Bean(在publisher和consumer都要写),可以使用快捷键ctrl + H快速找到所在的类
在这里插入图片描述
配置好Bean后再次运行单元测试发送一条消息
在这里插入图片描述
在mq控制台get两条消息,可以看到修改后的消息类型为json,占用的空间变小了,内容可读性好
在这里插入图片描述
在comsumer中定义一个监听器,参数类型与发送的消息类型保持一致
在这里插入图片描述
启动消费者程序,可以看到输出的消息,
投递消息时类型是Map然后mq会把消息转换为字节(json方式),取出消息时会把字节又转回Map类型

p100 MQ入门-16 业务改造

在这里插入图片描述
将黑马商城中的一个业务从同步调用改为异步调用
在这里插入图片描述
由于不是通知所有的服务,因此不能用fanout交换机,这里采用direct交换机
首先声明队列和交换机(在消费者里面去写),发送者是pay服务,消费者是trade交易服务
第一步引入依赖,第二步配置mq的地址,第三步配置消息转换器,第四步配置监听器指定队列交换机
在这里插入图片描述
在发送者和消费者中引入依赖
在这里插入图片描述
配置mq的地址,在发送者和消费者中都要配置,可以放到nacos的共享配置中
在这里插入图片描述
配置消息转换器,由于发送者和消费者中都要配置,这里就写在common中,但是现在这个包路径下无法被扫描到因此无法生效
在这里插入图片描述
因此在spring.factories文件中加上一句话,让它能被扫描到,让配置生效
在这里插入图片描述
注意这里由于在common中配置了mq消息转换器,因此所有的服务都需要配置amqp的依赖,那么可以在父工程中加入这个依赖,否则无法运行
在这里插入图片描述
在发送者中修改原有的业务逻辑,将使用同步调用的FeignClient方式改为使用MQ的方式,指定发送的交换机、routingkey、消息等,
为了防止队列的使用影响到原有的代码(比如队列出错导致这里发送者的代码报错),这里加上try-catch来捕获异常
在这里插入图片描述
在消费者(trade交易服务)中配置监听器,创建队列交换机配置绑定关系,然后在监听方法中写原有的controller调用的代码
在这里插入图片描述
运行发送者和消费者服务,在mq控制台中可以看到创建好的交换机并完成了队列绑定
在这里插入图片描述
订单创建成功,可以看到订单号
在这里插入图片描述
去数据库里面查这个订单,可以看到状态为1,未支付
在这里插入图片描述
通过用户余额的方式支付成功
在这里插入图片描述
用户的金额扣减成功
在这里插入图片描述
可以看到订单状态为2,已支付

p101 MQ高级-01 课程介绍

在这里插入图片描述
保证消息收发的高可靠性
在这里插入图片描述
消息可靠性对业务很重要,
比如支付成功后但是订单更新状态没有完成,就会造成严重问题
在这里插入图片描述
这可能由于一些问题导致的:
1,发送消息时的网络出现问题(发送者的可靠性)。2,消息代理出现问题。3,消费者服务出现问题。
而在做好前3种可靠性后,延迟消息作为一种兜底的方案来使用

p102 MQ高级-02 发送者可靠性-发送者重连

在这里插入图片描述
发送者重连机制默认是关闭的,需要通过配置打开
在这里插入图片描述
最大重试次数默认为3
在这里插入图片描述
通过配置打开重试机制,然后将mq服务关闭,然后启动发送者去往队列里面发消息,会出现连接失败,
可以看到它会尝试连接3次,间隔时间与预期设置的相符
在这里插入图片描述
这里的重试机制是阻塞式的,如果发送者的连接真的出现问题,它执行重试时会造成后续的代码被阻塞,影响性能

p103 MQ高级-03 发送者可靠性-发送者确认机制

在这里插入图片描述
通过发送者确认来确保发送消息到MQ的可靠性
在这里插入图片描述
未路由成功的消息也算成功,MQ返回ACK给发送者。因为这是发送者造成的错误,而不是MQ造成的,
临时消息指不需要持久化的消息(普通的消息会进行持久化)
持久消息必须要写入完成以下才算成功,返回ACK:1,到达交换机;2,进入队列;3,完成持久化
而临时消息由于不需要持久化,因此进入队列就算成功,返回ACK
在这里插入图片描述
在配置种开启2个确认机制
correlated机制与simple不同,它避免了同步等待,
在这里插入图片描述
这是publisher return(发送者返回)机制,发送失败时会记录日志
在这里插入图片描述
这是publisher confirm(发送者确认)机制,在发送消息的时候会多传一个参数cd(CorrelationData),
它首先用getFuture方法得到一个空的对象,而addCallback方法用于将来MQ执行完成后会把确认结果通过回调函数通知给cd的空对象

p104 MQ高级-04 发送者可靠性-发送者确认的代码实现

在这里插入图片描述
发送者确认的代码实现
在这里插入图片描述
在发送者的yaml中配置
在这里插入图片描述
Return callback定义在发送者的一个配置类中
在这里插入图片描述
Confirm callback定义在消息发送者中,测试消息正常发送情况下日志的输出结果
在这里插入图片描述
将消息的routingKey改为一个不存在的字符,会导致交换机路由失败,
在日志中可以看到return callback和confirm callback都收到了,并显示错误类型为路由失败
在这里插入图片描述
如果指定了一个并不存在的交换机名称,会返回nack消息发送失败

p105 MQ高级-05 MQ可靠性-数据持久化

在这里插入图片描述
MQ的数据持久化可以保证MQ的可靠性
在这里插入图片描述
解决方法是持久化和lazyMQ,这里先讲持久化
在这里插入图片描述
交换机、队列创建默认是持久化的(Durable),而Transient表示临时的不持久化
消息在发送时有投递模式,Non-persistent非持久化(默认)和Persistent持久化
在这里插入图片描述
往队列里面发送非持久化的消息和持久化的消息,然后将MQ重启,最后发现只有持久化的消息还在,其它消息不见了

现在,往队列里面发送一百万条消息进行测试
在这里插入图片描述
为了方便观察结果,关闭发送者确认(因为这会造成一定的性能代价)
在这里插入图片描述
由于消息默认是持久的,如果要发送非持久消息,要先定义一个消息体
在这里插入图片描述
非持久化的消息是基于纯内存模式,如果内存满了,它也会写入磁盘(因为内存装不下了只能先放在磁盘里),
而此时写入会造成阻塞(消息处理速度降至0),然后随着写入完成逐渐提升处理速度
在这里插入图片描述
如上图,通常情况是,内存满了之后写入磁盘引起性能下降(黄线下降),多出的消息写入完成后逐渐恢复性能(黄线上升)

接下来再看持久化的消息
在这里插入图片描述
将消息改为持久化模式,再进行一次测试
在这里插入图片描述
可以看到,消息进入内存和持久化的数量是一致的(而不是内存满了再进行持久化),它会同时进行这2个操作,
因此,这种持久化消息的优点不仅是保证消息丢失,而且不会造成阻塞

p106 MQ高级-06 MQ可靠性-LazyQueue

在这里插入图片描述
这节讲队列持久化,惰性队列,它直接将消息写入磁盘,
它对写磁盘做了优化,性能较好,推荐使用LazyQueue
在这里插入图片描述
为了减少从磁盘加载到内存所需时间的影响,它会监测消费者的处理速度,提前加载部分消息到内存
LazyQueue的优点:1,持久化消息(直接存入磁盘);2,不写入内存也就不会内存满了pageOut导致阻塞
在这里插入图片描述
在控制台中创建一个lazyQueue时需要指定参数
在这里插入图片描述
lazyQueue两种代码声明方式
在这里插入图片描述
同样地,也往lazyQueue里面发一百万的消息进行测试,可以看到一开始就出现在paged out中,而不是内存和持久化
在这里插入图片描述

p107 MQ高级-07 消费者可靠性-消费者确认机制

在这里插入图片描述
消费者返回给MQ的确认
在这里插入图片描述
有三种消息处理状态,ack、nack、reject
返回消息状态不是在消费者拿到消息后立即返回,而是处理消息后根据处理结果来返回
如果返回reject通常是消息的内容有问题,消息没有重试的必要(因此不能用nack)
在这里插入图片描述
接下来进行测试
在这里插入图片描述
将消费者的yaml配置文件的消费者确认模式设为none
在这里插入图片描述
然后用发送者Test测试类往队列里面发一条消息,进入控制台可以看到存在一条消息
在这里插入图片描述
然后在消费者打一个断点,debug启动
在这里插入图片描述
此时进入控制台查看,发现消息不见了,说明立即返回了一个ack,队列把消息删除了,
但此时消费者并没有执行完毕,说明是拿到消息后立即返回的

接下来将消费者确认模式改为auto来测试
同样地,进入上面的断点
在这里插入图片描述
进入控制台看到有一个未确认的消息
在这里插入图片描述
将这个断点放行,发现它又进入断点了(控制台输出里面再次监听到了消息),
说明消费者返回的是nack,那么队列会将消息再次投递给消费者(直至消费者宕机)
在这里插入图片描述
将消费者关闭后,进入控制台看到消息变回了ready

再测试一下抛出一个消息转换异常
在这里插入图片描述
同样地,进入到断点后,点击放行,发现它不会再次进入断点了,
说明队列没有再次重复发消息了,消息被拒绝了

p108 MQ高级-08 消费者可靠性-消费者失败重试机制

在这里插入图片描述
消费者失败重试机制
在这里插入图片描述
失败重试机制是消费者异常时本地重试,而不是重新入队再取出消息(消息在消费者和MQ之间踢皮球)
它默认是关闭的,需要在配置文件中打开

先测试一下现在的情况
在这里插入图片描述
在消费者中设定抛一个异常,然后往这个队列里面发一条消息,
启动消费者后它会不断地报错,说明这条消息一直在重复的发送(队列发给消费者,而不是消费者自己重试)
在这里插入图片描述
打开控制台可以看到每秒发了很多条消息(此时消费者和MQ都会有很大的性能消耗)
在这里插入图片描述
在消费者中打开失败重试,再用上面发消息的例子
在这里插入图片描述
失败重试3次后(默认3次),重试次数耗尽,消息就会被拒绝,此时消息既不在MQ也不在消费者中(可靠性降低了)
在这里插入图片描述
第一种方式是默认的,
第二种方式是重试耗尽后,重新入队
第三种方式是重试耗尽后,投入到指定交换机,Republish重发消息
在这里插入图片描述
接下来进行第三种方式的测试
在这里插入图片描述
在消费者里面创建一个配置类
在这里插入图片描述
重新用之前的例子进行测试,可以看到重试3次后,它会将消息发送到刚刚配置的交换机
在这里插入图片描述
进入控制台可以看到创建了error队列,并且里面有一条消息
在这里插入图片描述
查看这条消息,可以看到它带上了完整的异常栈信息(便于进行故障排查)
在这里插入图片描述

p109 MQ高级-09 消费者可靠性-业务幂等处理

在这里插入图片描述
以上的小节,保证了消息各方面的可靠性,但是可能出现消息被重复处理的可能性,
在这里插入图片描述
比如由于网络出现故障,网络恢复后再次投递一次消息,但是这条消息之前已经被处理过了,在某些业务中可能导致问题
解决消息重复消费问题就是要进行业务幂等处理,即业务执行一次或多次的业务状态影响是一样的
在这里插入图片描述
方案一
在这里插入图片描述
在发送者的消息转换器里面将消息id的设置打开,
然后往队列里面发送一条消息,之后观察结果
在这里插入图片描述
在控制台中可以看到这条消息的属性有一个id,那么消费者就可以将这个id存入数据库来判断消息是否被消费过,
接下来在消费者中查看这个id
在这里插入图片描述
将消费者的方法参数类型修改为Message(而不是之前的String),就可以获取到属性,从而拿到消息id
这种方案不推荐使用,业务里面会去判断与业务无关的消息这部分的逻辑,会造成业务侵入,而且也对业务性能有影响
在这里插入图片描述
第二种方案是在业务本身上增加健壮性,考虑到这种消息重发的场景,
上图的场景:
比如用户付款后通过MQ通知交易服务将订单标记为已支付,交易服务然后返回ack给MQ表示处理成功,
此时网络出现故障,MQ没有收到这个ack以为交易服务宕机了(接下来,消息会重新入队并发消息给交易服务)
此时,用户再点击申请退款(由交易服务完成),交易服务将订单改状态为退款中,
此时,MQ到交易服务的网络恢复,之前的MQ中的消息再次发送给交易服务,结果是造成订单状态从退款中被覆盖为已支付。
如果考虑到业务幂等就可以加上图红圈部分的逻辑,
在进行标记前先判断是否是未支付,可以得出是否是一条重复消息,如果为重复消息则不作标记直接结束
在这里插入图片描述
在交易服务的Listener中标记订单为已支付前加上这段逻辑,实现业务的幂等性
在这里插入图片描述
首先,支付服务和交易服务之间的订单状态一致性由MQ异步通知实现的,
然后,多种可靠性保证消息至少被消费一次,
最后,多次消费引起的幂等问题,是业务中的幂等判断避免的
服务消息处理失败的兜底方案见下节

p110 MQ高级-10 延迟消息-什么是延迟消息

在这里插入图片描述
延迟消息是在指定时间之后消费者才会收到的消息
在这里插入图片描述
比如说,用户下单后扣减了商品库存,
用户是否支付是由支付服务完成的,如果支付了支付服务需要通知交易服务修改订单状态为已支付,
但网络故障,支付服务给交易服务发送的消息没有收到,
这种情况下,交易服务就可以设置一个延迟任务,30分钟后去问一下支付服务是否用户支付了,
这样可以起到一个订单超时未支付自动取消的效果,
具体的做法是,在下单的时刻就往MQ里面发一个30分钟的延迟消息,
时间到了之后交易服务就会收到消息,从而去执行延迟任务

p111 MQ高级-11 延迟消息-死信交换机

在这里插入图片描述
使用死信交换机可以实现延迟消息
在这里插入图片描述
比如说,将normal.queue指定一个死信交换机dlx.direct,再往队列里面发送一条过期时间为30秒的消息,
由于这个队列是没有消费者监听的,那么30秒后它就会被发送到死信交换机,再投递到绑定的队列,
因此,消费者去监听这个队列就可以达到延迟消息的效果,
此外,这两个地方的交换机和队列的bindingKey需要设为一致(normal.direct到normal.queue、dlx.direct到dlx.queue)
在这里插入图片描述
在消费者中定义一个死信交换机并绑定队列
在这里插入图片描述
在消费者中定义一个normal交换机和队列并绑定它们,并指定队列的死信交换机,
这里创建交换机和队列用的是传统Bean的方式,而不是上面的@RabbitListener注解的方式,因为这个队列没有消费者
在这里插入图片描述
启动消费者后,进入MQ控制台可以看到交换机和队列都创建成功了,在队列中可以看到DLX标志,表示它指定了一个死信交换机
在这里插入图片描述
发送者发送一条消息,设置消息过期时间为10秒,
message是MessagePostProcessor是消息的后处理器,可以获取消息属性,来设置消息的过期时间
在这里插入图片描述
在消费者中可以看到10秒后收到了这条消息

p112 MQ高级-12 延迟消息-延迟消息插件

在这里插入图片描述
上一节的延迟消息是用死信交换机来实现的,这种方式配置比较复杂,可以用延迟消息插件来将交换机加上延迟功能
在这里插入图片描述
消息到达交换机后不会立刻进行路由,而是等待一段时间再路由到队列

插件可以在RabbitMQ社区下载也可以在GitHub下载,插件的版本要与MQ的版本一致
在这里插入图片描述
将插件复制到mq容器的/plugins目录下
在这里插入图片描述
然后先用命令docker exec -it mq进入容器,再用rabbitmq的插件命令启用这个插件,可以看到插件已启用
在这里插入图片描述
在代码中声明队列为延迟的队列的2种方式(注解方式和Bean方式),加上delayed表示这是一个延迟消息
在这里插入图片描述
在发送消息时需要设置消息的延迟时间,这里与上一节相同(都是通过MessagePostProcessor消息后处理器来完成),
不同的是,这里是setDelay(上一节是setExpiration)
在这里插入图片描述
在消费者中定义交换机和队列,并给交换机加上延迟属性
在这里插入图片描述
启动消费者,然后在控制台可以看到这个交换机的类型为延迟消息的交换机
在这里插入图片描述
启动发送者,发送一条延迟消息,时间设置为10秒
在这里插入图片描述
10秒后,消费者监听到了消息

这种延迟消息的实现是由cpu来完成计时的,是cpu密集型的任务,
如果有大量的消息需要计时,会给cpu带来很大的压力,
因此,尽量不要设置过长的延迟时间,否则会造成很多延迟消息需要计时,影响性能

p113 MQ高级-13 延迟消息-取消超时订单

在这里插入图片描述
在黑马商城中用延时消息解决用户未支付超时订单的问题
在这里插入图片描述
这里还是之前那个例子,假设支付服务向MQ发送消息的过程出现问题,支付服务和交易服务的一致性就会有问题,
那么无论支付服务是否能够成功通知交易服务,交易服务都应该有一个兜底方案(没有支付成功就取消订单恢复库存),
在这里插入图片描述
在下单逻辑结束前发送一个15分钟的延迟消息到队列,在15分钟后交易服务自己收到这条消息,然后去做后面的业务,
然后查询订单是否支付,如果已为支付就不作操作,
如果为未支付,有可能是支付服务的支付成功的消息还没有发到交易服务的情况,那么就去查支付服务的交易流水,
如果有支付的流水就改为已支付,没有就不作操作
在这里插入图片描述
由于交易服务要通过OpenFeign向支付服务发送请求,先定义好FeignClient需要的类
在这里插入图片描述
在订单创建业务的最后发送一条延迟消息到MQ,消息内容为订单id,延迟时间设为10秒
在这里插入图片描述
在交易服务的中定义一个消费者去监听这条延迟消息,收到消息后去做延迟的业务逻辑

接下来进行测试
在这里插入图片描述
首先将支付服务支付成功后发送MQ给交易服务的代码注释掉(用于通知交易服务修改订单为已支付),
为了模拟支付服务发送消息给MQ的故障情况
在这里插入图片描述
在商城里面下单并支付,显示支付成功
在这里插入图片描述
在交易服务的订单表里面可以看到这个订单的状态为2(已支付),支付时间是订单创建时间的10秒后,说明10秒后更新订单状态为已支付
在这里插入图片描述
在交易服务的日志中看到在10秒后,交易服务用PayClient向支付服务发了一个查询,发现订单已经支付了,就更新订单为已支付,
因此,实现了一种延迟查询的效果,作为交易服务订单业务的兜底方案

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值