RabbitMQ高级

背景

前面我们已经学会了MQ的基本使用,但是我们真正到企业实际开发中,消息业务难免会出现问题,问题的出现主要再这下面三个方面:

1.消息没发到消息代理
2.消息代理把消息弄丢了
3.消费者把消息弄丢了

因此我们如果要解决这样的消息可靠性的问题,我们就要在这三个方面下功夫去优化:
1.发送者可靠性、2.MQ(消息代理)可靠性、3.消费者可靠性

我们做完这三个优化后可以大大降低消息可靠性的问题,但是还是会有小概率发生,因此,我们还需要相关的兜底方案

发送者可靠性

发送者重连

发送者重连用于解决由于网络波动,导致发送者连接MQ失败的情况,因此我们可以修改配置文件让发送者重新连接MQ

各个参数在下图写的非常清楚了,这里就不多赘述了

我们修改自己发送方的配置文件,关闭rabbitMQ,直接运行我们发送消息的测试方法看看会是什么效果

结果如下:
可以看到,发送消息失败了,并且按照我们的配置文件所配置的,一共连接了3次,且每一次都是间隔1*1秒的等待时间

注意:发送者重连机制确实可以一定的解决该问题,但是该机制是阻塞式的,若“发送消息”后面还有其他代码,那么其他代码必须要等待我们重连完成后才会执行,因此这种阻塞式的代码会影响其他业务的性能,从而影响用户体验。所以我们要合理配置等待时长后重试次数

发送者确认

介绍

AMQP提供了PublisherConfirm和PublishReturn两种机制共同来确认判断消息在MQ的处理情况,如果在MQ处理的没问题,那就返回正常结果,反之返回异常结果。如果出现了异常结果,我们就可以尝试去做重连以及其他的一些异常处理

注意:发送者确认机制由于需要和MQ进行通信和确认,因此会大大影响到发送消息的效率,并且发送者和MQ连接这一块出现的异常比较少,因此真实企业开发不是特别推荐开启

返回结果介绍:

ACK:消息在MQ中处理正常所返回的结果,情况可以参照上图。这里补充一点,上图中第一条ACK情况:投递成功但是“路由失败”返回ACK,这里你可能会想,明明都“失败”了,为什么还有返回ACK呢?因为ACK返回的条件是MQ本身处理是否正常,如果路由异常,比如上图的exchange2交换机没有连接任何的队列,向他发送消息,必定路由失败出现异常,那么交换机没有连接队列是谁造成的呢?必然不是MQ本身,而是我们程序员没有配置好MQ的交换机或者是在用代码生成交换机和队列的时候没有写好所造成的,这种错误不来源于MQ本身,来源于外部(比如作为程序员的我们),因此返回的是ACK,同理还有其他类似情况如routingkey配置错误导致路由失败的,同样返回的是ACK

NACK:就是投递失败出现的异常结果,这就是MQ本身出现问题了,比如说“持久消息”投递到了MQ,但是并没有完成持久化,此时就会返回NACK

介绍完基础部分我们来介绍如何开启“发送者确认”机制吧

启动步骤

1.文件配置

修改相关yml文件,开启我们前文所说实现“发送者确认”的PublisherConfirm和PublishReturn两种机制,其中PublisherConfirm有三种模式,simple就是我们前文有所提及的“阻塞式”的同步执行,会影响业务性能,而correlated是异步的方式,因此我们更加推荐使用correlated方式启动

2.PublishReturn机制开启

在publisher新建config包,在里面新建配置类即可

3.PublisherConfirm机制开启

使用该机制发送消息的代码如下图,仔细一看图片,最下面一行不就是我们之前写发送消息的代码吗,只不过多了一个“cd”参数,因此我们到时候优化修改代码就是优化我们之前写的发消息的代码,接下来我们一一简单介绍:

首先是多了的cd参数,该参数是一个对象,这个对象中包含了两个重要的属性:
1.id:用于标识每一个消息,将来消息很多,需要通过id才知道哪一个消息出现问题
2.future:异步得到确认结果的关键

关于上文所讲的future,这里可以简单介绍一下,我们之前编写yml文件使用了correlated即异步回调方式返回确认结果,这里的future就是用于实现异步回调的确认结果的,对于确认结果我们不需要等待他返回才去执行下面的代码,我们可以直接返回一个future对象,然后什么时候确认结果返回了,就可以利用future对象执行回调方法得到确认结果。(下图标红框的代码可以参照)

剩余代码onFailure/onSuccess就是用于判断MQ处理是否正常,其中onfailure不用特别注意,基本不会触发,ACK/NACK的判断在onSuccess方法

最后我们可以看发送者发送消息到MQ的成功与否了

MQ可靠性

MQ默认接收到消息是存储在MQ的内存当中的,因为这样无论消息的读或写速度都比较快,但是这也带来一个问题,当大量的消息发送到MQ中,MQ的内存队列直接满了,此时如果还想要接收消息,MQ就会“被动的”把之前比较老的消息写入磁盘中,这样就可以给内存队列腾出空间从而又可以接收新的信息了,但是“写”的动作是非常耗时的,因此在“写”的过程中,队列可能依旧是满的状态,此时外部新发来的消息就会丢失

为了解决这个问题,就要在MQ上做处理

数据持久化

简单来说就是要把exchange交换机、队列queue、发送的消息message让其的类型都变为durable持久化的形式,但是由于我们使用SpringAMQP发送的,因此默认来说全部都是持久化,我们无需做任何的操作,在这里我们就不过多演示赘述了

LazyQueue

这里也是简单说一下,因为从3.12版本开始,RabbitMQ的队列默认都是LazyQueue,因此这一块优化官方以及帮我们做好了,我们不用怎么管,只不过我们这次学习用的还是3.8,因此简单了解一下即可

对于非LazyQueue的“传统队列”,面对高并发的消息非常难处理,因此这时就要开启LazyQueue

开启的方式也很简单,对于控制台,直接选择“lazymode”即可

对于代码,和以前生成交换机和队列形式一样,使用声明bean对象或者注解的方式即可,这里不多赘述了

消费者可靠性

消费者确认机制

前文我们讲过了发送者确认机制,消费者确认机制也是同理,通过消费者是否正确处理消息所返回的确认结果来做异常的处理

这里我们需要重点提醒的是,返回确认结果的时机,一定是消息发送到消费者,并且消费者处理完成后这些消息才返回确认结果,不能是消息一发送给消费者就返回确认结果,这样确认消息就没有什么意义了

确认结果:
ACK/NACK:和消费者确认的确认结果一样,只不过他们新增了机制,ACK的返回代表了消息被消费者成功处理,并且这个成功处理的消息在处理完成后要被删除(很好理解,比较都处理完成了,就不要留在队列里面占用内存了)。NACK的返回代表消息处理失败,并且这个失败可能是消费者没有处理好等其他问题,因此需要重新给消费者发一次消息,直到消费者处理成功即可。
reject:也是消息处理失败的返回结果,只不过这个“失败”的原因并不来源于消费者,更多的是来源于消息本身,可能消息的内容、格式本身就是错误的,因此发送reject结果直接拒绝处理该消息,因此该消息直接会被RabbitMQ删除。(毕竟如果消息本身就是错误的,重新发给消费者处理再多次都没有用)

开启

启动也很简单,只需要再yml文件修改配置即可
一共有三种状态,none就是我们之前讲过的消息一发送到消费者就立马返回结果,这是十分不推荐的,manuel手动模式,可以自定义再任意时刻返回确认信息,比较灵活但是需要自己手写代码,一般是定制化需求会用,这里不做演示。auto自动模式,就是我们上文讲解的逻辑,在“合适的时候”(上文讲的返回情况)返回相应的确认结果

消费者重试

我们前文讲的消费者确认机制,当返回NACK时,会把消息再次发送给消费者做消息处理,直到成功位置。但是如果出现一些极端情况,比如这个错误一直没有得到处理,那么这个消息就会一直发送给消费者处理,一直重复循环,而消息的发送量是比较大的,一秒内发送一千多条,那么这样就会占用不必要的资源,因此我们使用失败重试机制,当消费者处理消息出现异常时,先不是一直发消息给消费者,而是在本地进行重试,再一条条的发送给消费者试,直到成功为止(这个需要配置),这里再发送给消费者,消息流量就会小特别多,可能几秒钟才几条

基本配置和我们之前的发送者重连的配置差不多,这里不多赘述,指的一提的就是stateless这个配置,就是你的业务中是否包含了事务,如果包含了就填写false,不包含就填写true

我们配置好后,主动抛出一个异常,看看消费者重试默认时如何处理的

可以看到,按照了我们配置文件的配置方式本地重试了三次,但是我们发现一个问题,重试完后消息直接就被“reject”丢掉了,那么这条消息就变相的丢失了,这明显不符合需求

因此,再消费者重试机制我们还需要再次配置其他的策略

刚刚的reject策略是默认的策略,直接丢弃消息,更加推荐的是下图中的下面两种策略,其中ImmeadiateRequeueMessageRecover就是我们之前说的“慢慢的一条条重新入队给消费者处理”,另一个RepublishMessageRecover是一种方案,当重试耗尽后,把失败消息发送给指定的交换机,这个交换机再发送给指定的队列,这样我们开发人员就可以从队列获取到错误信息,这样子开发人员就可以介入从而处理异常

我们下面来演示将失败消息投递到指定交换机

RepublishMessageRecover方案实例

我们新建配置类,做好错误交换机/队列以及失败处理策略的配置

然后我们再来测试错误:

控制台成功显示把错误信息发送给特定交换机了

来到rabbitMQ控制台,可以成功看到错误消息以及错误原因,非常方便程序员介入处理

业务幂等性

幂等性用在我们软件业务上也很好理解,一个业务执行多次它的结果都是一样的,那么这个业务就是幂等的,比如查询商品就是幂等的,扣减库存就不是幂等的,因为多次执行库存就会一直减少

了解完幂等的概念,我们MQ对于非幂等的业务这里还会出现部分差错,比如:
MQ给消费者发送“扣减库存”的消息,消费者执行完成后,返回ACK给MQ,但是此时,MQ和消费者之间的网络断开了,导致消费者的ACK没有发送给MQ,MQ就会认为消费者宕机了没有收到它发送的消息,于是又会准备发“扣减库存”的消息给消费者,当网络恢复时,MQ再次向“以为宕机”的消费者发送扣减库存的消息,消费者再次接收消息,执行相关服务,于是库存就又被扣减了

对于非幂等的业务,我们有两种主要的处理方案

唯一消息ID

给MQ里面的消息都“设置”唯一ID,消费者接收消息后处理好业务把消息ID保存到数据库,如果下次又收到了相同消息,只需要去数据库查看该消息ID是否存在,若存在则放弃处理

这个ID时MQ消息的一种属性,我们可以这样设置:
在我们上一篇文章末尾介绍的消息转换器,把所发送的消息转换为MQ专属的“Message”类型对象,ID就是Message对象中的一个属性,因此我们在消息转换器中打开相关设置,然后消费者接收消息的时候去get message对象以及它的ID属性即可

因此,我们在发送方的启动类修改一下我们之前定义的消息转换器,把自动创建消息id的值改为true

我们设置完尝试发送消息,发现队列中的消息成功附赠id属性

发送方设置完了,我们消费者接收也得接收相应类型的参数,我们前文提过的Message对象

消费者也能成功接收,并且从对象中提取出相关属性

业务判断

这个其实就是对于特定的业务做相关的优化了,不算很泛的一种方式,这里就简单讲一下吧,实际开发中需要更具特定的业务来思考要怎么样优化

场景:
支付服务和交易服务,用户点击支付,交易服务吧订单标记为已支付,但是此时MQ和消费者之间的网络断开了,没有返回ACK,因此MQ又准备发送同样的“标记支付”消息。但是此时用户又想退款了,并且此时MQ和消费者之间的网络又好了,那么先前的“标记支付”的消息就会直接覆盖掉用户这次“退款消息”

解决方案也很简单:消费者接收到消息去执行相关订单状态操作的业务时,去查看当前订单状态,若当前订单状态和现在一样,则不执行该业务/不管该消息,比如现在就是已支付状态,又让我干一次“已支付”那就不干了

延迟消息

设想一个场景:
假设创建一个订单,首先通过“交易服务”创建保存订单信息,再通过“商品服务”来扣减相关商品的库存,同时调用“支付服务”来更新订单状态(是否支付)。此时如果有某一些不可抗力的因素导致“支付服务”的返回结果无法返回给交易服务,导致订单的状态一直是“未支付”,这就会导致很大的问题,假设用户已经支付了,但是又显示未支付,体验非常差。假设用户没支付,但是前面调用“商品服务”的时候,商品库存已经减少了,导致别的用户无法购买该商品,同样体验非常差

因此我们可以使用延迟消息来解决该问题

交易服务创建完订单后可以设置一个“闹钟”,让这个“闹钟”再设定时间段叫醒自己去查看支付服务的的返回结果,若支付服务有相关返回结果,那么就更新订单状态,如果没有返回结果,这说明“支付超时”,则取消订单,复原商品的库存

延迟消息就是用来充当这个“闹钟”的,交易服务通过发送延迟消息到MQ,指定时间后发送消息给交易服务,让交易服务去查询支付服务的返回结果

那我们要如何实现“延迟消息”呢,我们的rabbitMQ本身并不具备“延迟消息”的功能,但是我们可以通过里面其他属性去“模拟”延迟消息从而达到我们所想要的效果

死信交换机

顾名思义,就是由“死信”和“交换机”共同组成的

先来了解一下什么是“死信”(如下图所示),其中对于实现“延迟消息”最重要的就是其中的第二条:如果消息是一个过期消息,并且超时无人消费,那么该消息就会变成“死信”

了解完这个特性后,我们通过死信交换机来实现延迟消息就非常容易了:

我们先创建一个普通的交换机和队列,但是这个队列不绑定任何的消费者(重点!),这样我们如果发送一些定期超时消息,由于没有人消费,留在队列里面的消息就会变成死信,然后再在该队列中绑定另一组队列和交换机(注意:这里的绑定需要使用dead-letter-exchange=绑定的交换机名称,来绑定,这样子队列中的死信就会进入所绑定的交换机),这一组交换机和队列就会接收所绑定的队列发来的死信,最后再发送给消费者

我们举例子来看,假设我们发一个30s过期的消息到第一组交换机和队列中,由于第一组交换机队列没有任何的消费者,那么该消息等到30s后会自动变为死信,而死信就会接着进入我们绑定的死信交换机队列再发送给消费者。我们去除过程只看结果,我们发送的定期过期消息不就变成了”延迟消息“了吗,定期过期多久就代表了延迟多久发送到消费者

实践

接下来我们来试着实践一下,其实思路很简单,无非就是创建两组队列和交换机,然后用特定的“死信”绑定方式来绑定第一组队列和第二组死信交换机即可

首先先来声明死信交换机以及它的队列和消费者:

再来声明绑定死信交换机的普通队列和交换机,由于普通队列不能有消费者,因此只能用bean对象的方式声明,不能像上面死信交换机一样使用注解的方式声明

要注意,普通队列绑定其死信交换机所用到的key要和死信交换机绑定它的死信队列所用的key保持完全一致,这里可以看到都是“dead”

发送延迟消息

最后我们来看测试结果:
消息在31秒的时候发出

消费者在36秒的时候才接收到

完全符合我们使用死信交换机实现延迟消息

延迟消息插件

上文讲解了死信交换实现延迟消息,但是过于麻烦你,社区中已经出现了专门设置交换机为“延迟消息交换机”的插件,从而实现延迟消息

使用也非常简单,就是在交换机加一个delay属性即可

声明好消费者

最后编写好测试方法

可以看到成功实现了延迟消息

相关新闻

前英特尔CEO最新判断:绝大部分芯片设计公司将会消失,制造才是唯一的壁垒

前英特尔CEO最新判断:绝大部分芯片设计公司将会消失,制造才是唯一的壁垒

在这一波AI浪潮里,AI算力无疑是最大赢家,英伟达一跃成为全球市值最高的公司之一。 英伟达凭借GPU一跃成为全球市值最高的公司之一,也带动了整个AI芯片创业市场的繁荣。短短几年,大量创业公司涌入,试图从英伟达手中分走…

2026/10/21 2:28:55 阅读更多 →
宇宙(下):节点自治与种群网络——拓扑有了,路由没有

宇宙(下):节点自治与种群网络——拓扑有了,路由没有

宇宙(下):节点自治与种群网络——拓扑有了,路由没有 系列:《宪法即代码》第 35 篇(下)| 标签建议:AI编程、Rust、分布式系统、多智能体、防御性公开 文章目录宇宙&#x…

2026/10/21 2:28:55 阅读更多 →
喷砂工艺介绍

喷砂工艺介绍

喷砂是用压缩空气将棱角状磨料加速,让它高速打在工件表面上,靠磨料自身的切削和撞击把氧化皮、锈层、油污还有旧涂层带走,同时留下一层凹凸的纹路。这道工序一般排在涂装、热喷涂、电镀或者粘接之前,做的是为后一道工序准备界面的…

2026/10/21 2:28:55 阅读更多 →

最新新闻

51单片机DS18B20温度采集与NRF905无线传输实战

51单片机DS18B20温度采集与NRF905无线传输实战

简介:这是一套面向单片机爱好者、电子类专业学生与DIY玩家的温度采集与无线传输实践方案,基于51单片机搭配NRF905无线模块,解决短距离温度数据采集与无线收发的问题,适合具备一定电子技术基础、能看懂基础电路图并了解C语言设计原…

2026/10/21 3:09:09 阅读更多 →
YOLOv8/YOLO26目标检测实战:从环境搭建到模型部署完整指南

YOLOv8/YOLO26目标检测实战:从环境搭建到模型部署完整指南

作为一个前前后后用YOLO折腾过不止一个项目的人,我太清楚新手第一次接触YOLO时那种“明明照着教程一步步点,怎么到我这就疯狂报错”的无助感了。这个标题里说的“全网最稳”,其实并不是空话——它邀请你走的,是一条我踩了无数坑之…

2026/10/21 3:09:09 阅读更多 →
DeepSeek Harness 实操:从零跑通智能体框架,25 分钟搭好 Web 界面与第一个插件

DeepSeek Harness 实操:从零跑通智能体框架,25 分钟搭好 Web 界面与第一个插件

DeepSeek Harness 实操:从零跑通智能体框架,25 分钟搭好 Web 界面与第一个插件 【免费下载链接】deepseek-harness DeepSeek Harness: Everything is a Plugin. 项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness DeepSeek Harness(dsh)是 DeepSeek 开源的…

2026/10/21 3:09:09 阅读更多 →
数学建模竞赛真题解读与源码实战:从模型构建到论文输出全链路

数学建模竞赛真题解读与源码实战:从模型构建到论文输出全链路

简介:一套面向高校数学建模竞赛的完整培训材料,涵盖基础理论、建模方法、真题解析与源代码实现,适合从入门到备赛冲刺的各阶段学生。压缩包共61个文件,约2.02MB,以30个MATLAB脚本、16个Word文档和6个Excel表格为主&…

2026/10/21 3:09:09 阅读更多 →
高职院校智能体应用生态建设:OpenClaw落地实战指南

高职院校智能体应用生态建设:OpenClaw落地实战指南

1. 项目概述:为什么要研究OpenClaw在高职院校的落地先说结论:OpenClaw这个项目本身不是什么新概念,它本质上是一套开源的多智能体编排框架。我接触它是在一次校内数字化转型研讨会上,某高校信息中心的某老师提到他们正在试水智能体…

2026/10/21 3:09:09 阅读更多 →
数据获取与处理实战:从数据源选择到数据清洗的完整指南

数据获取与处理实战:从数据源选择到数据清洗的完整指南

这几年做数据相关的工作,被问得最多的一个问题是:“我该从哪儿拿数据?拿到手之后又该怎么处理?”说实话,大多数人在第一步就卡住了,要么不知道怎么稳定地拿到数据,要么千辛万苦拿到一份Excel&am…

2026/10/21 3:08:09 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →