RabbitMQ之发布确认高级
RabbitMQ之发布确认高级
- 一、发布确认 SpringBoot 版本
- 1.1 确认机制方案
- 1.2 代码架构图
- 1.3 配置文件
- 1.4 添加配置类
- 1.5 消息生产者
- 1.6 回调接口
- 1.7 消息消费者
- 1.8 结果分析
- 二、回退消息
- 2.1 Mandatory 参数
- 2.2 消息生产者代码
- 2.3 回调接口
- 2.4 结果分析
- 三、备份交换机
- 3.1 代码架构图
- 3.2 修改配置类
- 3.3 报警消费者
- 3.4 测试注意事项
- 3.5 结果分析
在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢?特别是在这样比较极端的情况,RabbitMQ 集群不可用的时候,无法投递的消息该如何处理呢:
应 用 [xxx] 在 [08-1516:36:04] 发 生 [ 错 误 日 志 异 常 ] , alertId=[xxx] 。 由
[org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:start:620] 触 发 。
应用 xxx 可能原因如下
服 务 名 为 :
异 常 为 : org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:start:620,
产 生 原 因 如 下 :1.org.springframework.amqp.rabbit.listener.QueuesNotAvailableException:
Cannot prepare queue for listener. Either the queue doesn’t exist or the broker will not
allow us to use it.||Consumer received fatal=false exception on startup:
一、发布确认 SpringBoot 版本
1.1 确认机制方案

1.2 代码架构图

1.3 配置文件
在配置文件当中需要添加
spring.rabbitmq.publisher-confirm-type=correlated
- NONE
禁止发布确认模式,是默认值 - CORRELATED
发布消息成功到交换机后会触发回调方法 - SIMPLE
经测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法 等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是watiForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker。
spring.rabbitmq.host=192.168.10.128
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123
spring.rabbitmq.publisher-confirm-type=correlated
1.4 添加配置类
@Configuration
public class ConfirmConfig {public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";public static final String CONFIRM_QUEUE_NAME = "confirm.queue";//声明业务 Exchange@Bean("confirmExchange")public DirectExchange confirmExchange() {return new DirectExchange(CONFIRM_EXCHANGE_NAME);}// 声明确认队列@Bean("confirmQueue")public Queue confirmQueue() {return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();}// 声明确认队列绑定关系@Beanpublic Binding queueBinding(@Qualifier("confirmQueue") Queue queue, @Qualifier("confirmExchange") DirectExchange exchange) {return BindingBuilder.bind(queue).to(exchange).with("key1");}
}
1.5 消息生产者
@RestController
@RequestMapping("/confirm")
@Slf4j
public class Producer {public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";@Autowiredprivate RabbitTemplate rabbitTemplate;@Autowiredprivate MyCallBack myCallBack;// 依赖注入 rabbitTemplate 之后再设置它的回调对象@PostConstructpublic void init() {rabbitTemplate.setConfirmCallback(myCallBack);}@GetMapping("sendMessage/{message}")public void sendMessage(@PathVariable String message) {//指定消息 id 为 1CorrelationData correlationData1 = new CorrelationData("1");String routingKey = "key1";rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME, routingKey, message + routingKey, correl ationData1);CorrelationData correlationData2 = new CorrelationData("2");routingKey = "key2";rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME, routingKey, message + routingKey, correl ationData2);log.info("发送消息内容:{}", message);}
}
1.6 回调接口
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {/*** 交换机不管是否收到消息的一个回调方法* CorrelationData* 消息相关数据* ack* 交换机是否收到消息*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {String id = correlationData != null ? correlationData.getId() : "";if(ack) {log.info("交换机已经收到 id 为:{}的消息", id);} else {log.info("交换机还未收到 id 为:{}消息,由于原因:{}", id, cause);}}
}
1.7 消息消费者
@Component
@Slf4j
public class ConfirmConsumer {public static final String CONFIRM_QUEUE_NAME = "confirm.queue";@RabbitListener(queues = CONFIRM_QUEUE_NAME)public void receiveMsg(Message message) {String msg = newString(message.getBody());log.info("接受到队列 confirm.queue 消息:{}", msg);}
}
1.8 结果分析

可以看到,发送了两条消息,第一条消息的 RoutingKey 为“key1”,第二条消息的 RoutingKey 为“key2”,两条消息都成功被交换机接受,也收到了交换机的确认回调,但消费者只收到了一条消息,因为第二条消息的 RoutingKey 与队列的 BindingKey 不一致,也没有其他队列能接受这个消息,所以第二条消息被直接丢弃了。
交换机发出了确认回调,但实际上队列没有收到消息。
二、回退消息
2.1 Mandatory 参数
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。那么如何让无法被路由的消息帮我想办法处理一下?最起码通知我一声,我好自己处理啊。通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。
2.2 消息生产者代码
@Slf4j
@Component
public class MessageProducer implements RabbitTemplate.ConfirmCallBack,RabbitTemplate.ReturnCallback {@Autowiredprivate RabbitTemplate rabbitTemplate;//rabbitTemplate 注入之后就设置该值@PostConstructprivate void init() {rabbitTemplate.setConfirmCallback(this);/*** true:* 交换机无法将消息进行路由时,会将该消息返回给生产者* false:* 如果发现消息无法进行路由,则直接丢弃*/rabbitTemplate.setMandatory(true);//设置回退消息交给谁处理rabbitTemplate.setReturnCallback(this);}@GetMapping("sendMessage")public void sendMessage(String message) {//让消息绑定一个 id 值CorrelationData correlationData1 = new CorrelationData(UUID.randomUUID().toString());rabbitTemplate.convertAndSend("confirm.exchange", "key1", message + "key1", correlationData1);log.info("发送消息 id 为:{}内容为{}", correlationData1.getId(), message + "key1");CorrelationData correlationData2 = new CorrelationData(UUID.randomUUID().toString());rabbitTemplate.convertAndSend("confirm.exchange", "key2", message + "key2", correlationData2);log.info("发送消息 id 为:{}内容为{}", correlationData2.getId(), message + "key2");}@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {String id = correlationData != null ? correlationData.getId() : "";if(ack) {log.info("交换机收到消息确认成功, id:{}", id);} else {log.error("消息 id:{}未成功投递到交换机,原因是:{}", id, cause);}}@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {log.info("消息:{}被服务器退回,退回原因:{}, 交换机是:{}, 路由 key:{}", new String(message.getBody()), replyText, exchange, routingKey);}
}
2.3 回调接口
@Component
@Slf4j
public class MyCallBack implements
RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {/*** 交换机不管是否收到消息的一个回调方法* CorrelationData* 消息相关数据* ack* 交换机是否收到消息*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {String id = correlationData != null ? correlationData.getId() : "";if(ack) {log.info("交换机已经收到 id 为:{}的消息", id);} else {log.info("交换机还未收到 id 为:{}消息,由于原因:{}", id, cause);}}//当消息无法路由的时候的回调方法@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {log.error(" 消 息 {}, 被 交 换 机 {} 退 回 , 退 回 原 因 :{}, 路 由 key:{}", new String(message.getBody()), exchange, replyText, routingKey);}
}
2.4 结果分析

三、备份交换机
有了 mandatory 参数和回退消息,我们获得了对无法投递消息的感知能力,有机会在生产者的消息无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。而且设置 mandatory 参数会增加生产者复杂性,需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息,又不想增加生产者的复杂性,该怎么做呢?
前面在设置私信队列的文章中,我们提到,可以为队列设置私信交换机来存储那些处理失败的消息,可是这些不可路由的消息根本没机会进入到队列,因此无法使用私信队列来保存消息。在 RabbitMQ 中,有一种叫做备份交换机的机制存在,可以很好的应对这个问题。什么是备份交换机呢?备份交换机可以理解为 RabbitMQ 中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时,就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout,这样就能把素有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会进入这个队列中了。当然,我们还可以建立一个报警队列,用独立的消费者进行检测和报警。
3.1 代码架构图

3.2 修改配置类
@Configuration
public class ConfirmConfig {public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";public static final String CONFIRM_QUEUE_NAME = "confirm.queue";public static final String BACKUP_EXCHANGE_NAME = "backup.exchange";public static final String BACKUP_QUEUE_NAME = "backup.queue";public static final String WARNING_QUEUE_NAME = "warning.queue";// 声明确认队列@Bean("confirmQueue")public Queue confirmQueue() {return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();}//声明确认队列绑定关系@Beanpublic Binding queueBinding(@Qualifier("confirmQueue") Queue queue, @Qualifier("confirmExchange") DirectExchange exchange) {return BindingBuilder.bind(queue).to(exchange).with("key1");}//声明备份 Exchange@Bean("backupExchange")public FanoutExchange backupExchange() {return new FanoutExchange(BACKUP_EXCHANGE_NAME);}//声明确认 Exchange 交换机的备份交换机@Bean("confirmExchange")public DirectExchange confirmExchange() {ExchangeBuilder exchangeBuilder = ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true)//设置该交换机的备份交换机.withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME);return(DirectExchange) exchangeBuilder.build();}// 声明警告队列@Bean("warningQueue")public Queue warningQueue() {return QueueBuilder.durable(WARNING_QUEUE_NAME).build();}// 声明报警队列绑定关系@Beanpublic Binding warningBinding(@Qualifier("warningQueue") Queue queue, @Qualifier("backupExchange") FanoutExchange backupExchange) {return BindingBuilder.bind(queue).to(backupExchange);}// 声明备份队列@Bean("backQueue")public Queue backQueue() {return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();}// 声明备份队列绑定关系@Beanpublic Binding backupBinding(@Qualifier("backQueue") Queue queue, @Qualifier("backupExchange") FanoutExchange backupExchange) {return BindingBuilder.bind(queue).to(backupExchange);}
}
3.3 报警消费者
@Component
@Slf4j
public class WarningConsumer {public static final String WARNING_QUEUE_NAME = "warning.queue";@RabbitListener(queues = WARNING_QUEUE_NAME)public void receiveWarningMsg(Message message) {String msg = new String(message.getBody());log.error("报警发现不可路由消息:{}", msg);}
}
3.4 测试注意事项
重新启动项目的时候需要把原来的 cofirm.exchange 删除,因为我们修改了其绑定属性,不然报错:

3.5 结果分析

mandatory 参数与备份交换机可以一起使用的时候,如果两者同时开启,消息究竟何去何从?谁优先级高,经过上面结果显示答案是备份交换机优先级高.
相关文章:
RabbitMQ之发布确认高级
RabbitMQ之发布确认高级 一、发布确认 SpringBoot 版本1.1 确认机制方案1.2 代码架构图1.3 配置文件1.4 添加配置类1.5 消息生产者1.6 回调接口1.7 消息消费者1.8 结果分析 二、回退消息2.1 Mandatory 参数2.2 消息生产者代码2.3 回调接口2.4 结果分析 三、备份交换机3.1 代码架…...
lv5 嵌入式开发-10 信号机制(下)
目录 1 信号集、信号的阻塞 2 信号集操作函数 2.1 自定义信号集 2.2 清空信号集 2.3 全部置1 2.4 将一个信号添加到集合中 2.5 将一个信号从集合中移除 2.6 判断一个信号是否在集合中 2.7 设定对信号集内的信号的处理方式(阻塞或不阻塞) 2.8 使进程挂起(…...
【postgresql】 ERROR: multiple assignments to same column “XXX“
Cause: org.postgresql.util.PSQLException: ERROR: multiple assignments to same column "XXX"; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: ERROR: multiple assignments to same column "XXX"; 原因:or…...
一文读懂Llama 2(从原理到实战)
简介 Llama 2,是Meta AI正式发布的最新一代开源大模型。 Llama 2训练所用的token翻了一倍至2万亿,同时对于使用大模型最重要的上下文长度限制,Llama 2也翻了一倍。Llama 2包含了70亿、130亿和700亿参数的模型。Meta宣布将与微软Azure进行合…...
完整指南:如何使用 Node.js 复制文件
文件拷贝指的是将一个文件的数据复制到另一个文件中,使目标文件与源文件内容一致。Node.js 提供了文件系统模块 fs,通过该模块可以访问文件系统,实现文件操作,包括拷贝文件。 Node.js 中文件拷贝方法 在 Node.js 中,有…...
ElementUI - 主页面--动态树右侧内容管理
一.左侧动态树 1.定义组件 ①样式&数据处理 <template><el-menu class"el-menu-vertical-demo" background-color"#334157"text-color"#fff" active-text-color"#ffd04b" :collapse"collapsed" router :def…...
全国排名前三的直播公司无锋科技入驻天府蜂巢成都直播产业基地
最近,全国排名前三的直播公司——无锋科技,正式宣布入驻位于成都的天府蜂巢直播产业基地,这一消息引起了业内人士的高度关注。成都直播产业基地一直是中国直播产业的重要地标之一,其强大的技术和资源优势为众多直播公司提供了广阔…...
机器人中的数值优化|【五】BFGS算法非凸/非光滑处理
机器人中的数值优化|【五】BFGS算法的非凸/非光滑处理 往期内容回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法,可行牛顿法的python实现,以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化࿰…...
ESP32S3的MPU-6050组件移植教程
前言 (1)实习公司要搞ESP32BOX的驱动移植,所有资料自己找还是比较折磨人的现在我分享几个官方的组件移植资料: <1>Find the most exciting ESP-IDF components(ESP32的官方组件都可以在里面查,按照他…...
excel筛选后求和
需要对excel先筛选,后对“完成数量”进行求和。初始表格如下: 一、选中表内任意单元格,按ctrlshiftL,开启筛选 二、根据“部门”筛选,比如选择“一班” 筛选完毕后,选中上图单元格,然后按alt后&…...
pyspark 检测任务输出目录是否空,避免读取报错
前言 在跑调度任务时候,有时候子任务需要依赖前置任务的输出,但类似读取 Parquet 或者 Orc 文件时,如果不判断目录是否为空,在输出为空时会报错,所以需要 check 一下,此外Hadoop通常在写入数据时会在目录中…...
「网页开发|前端开发|Vue」10 vuex模块化:将数据划分成不同modules分别管理
本文主要介绍如何使用vuex的modules将状态数据根据不同模块进行划分并分别管理以及如何使用mapGetters快速将状态管理中的数据导入成local变量。 文章目录 本系列前文传送门一、场景说明二、使用modules划分不同模块三、使用Getters获取状态管理数据Getter传参mapGetters 辅助…...
苹果CMS插件-苹果CMS全套插件免费
网站内容的生成和管理对于网站所有者和内容创作者来说是一个挑战。有一些强大的工具可以帮助您轻松地解决这些问题。苹果CMS插件自动采集插件、采集发布插件以及采集伪原创发布插件,是这些工具之一。它们不仅可以极大地节省您的时间和精力,还可以提高您网…...
域环境介绍
一、概述 内网也指局域网,指的是某个区域由多台计算机互连而成的计算机组,范围通常在数千米以内,在局域网中,可以实现文件管理,应用软件共享,打印机共享、工作组内的日程安排、电子邮件和传真通信服务等&a…...
地球同步静止轨道上的中国卫星
3万6千公里地球同步静止轨道上的中国控制的卫星(包括香港属非国产平台卫星、外国属中国平台卫星),共80颗;截止到2023年8月3日,共有563颗在轨卫星。 号定位名称发射时间用途重量1141.1W中星1C(FH2C)2015.12.10DFH4平台…...
HAProxy代理TCP(使用HAProxy 为TiDB-Server 做负载均衡)
目录 一、使用HAProxy 为TiDB-Server 做负载均衡环境1、创建文件夹2、配置haproxy.cfg3、创建 docker-compose.yaml 文件haproxy.cfg 配置说明[参照官方文档](https://pingcap.com/docs-cn/v3.0/reference/best-practices/haproxy/ "参照官方文档") 一、使用HAProxy …...
全新自适应导航网模板 导航网系统源码 网址导航系统源码 网址目录网系统源码
高价值目录网导航网整站源码 | 2999元价值,最新版本源码下载推荐 1、导航网一键获取目标站SEO信息,7.5版本增加会员中心一键获取网站信息网站权重,增加小程序提交发布,全新自适应模板; 2、可设置游客提交、游客提交人工审核,会员免审提交,会员提交人工审核,VIP会员免…...
无人直播间
失败!! 采用 ffmpeg 技术进行推流 推流代码: 【需要将rtmp替换为你的推流地址】 ffmpeg -re -stream_loop -1 -i "rain.mp4" -c copy -f flv ""推流地址获取 以哔哩哔哩为例 点击下方链接 开播设置 - 个人中心 - …...
Linux 服务器防止 ssh 暴力密码登录破解之使用 fail2ban
前言,网络安全越来越重要,如何保证网站安全至关重要,在使用 Linux 服务器时,如果未设置有效安全登录屏障,每日将会有数百甚至数万次的密码暴力尝试登录,本篇章将介绍两种 Linux 登录安全防护 一࿱…...
第十四届蓝桥杯大赛软件赛决赛 C/C++ 大学 B 组 试题 D: 合并数列
[蓝桥杯 2023 国 B] 合并数列 【问题描述】 小明发现有很多方案可以把一个很大的正整数拆成若干正整数的和。他采取了其中两种方案,分别将他们列为两个数组 { a 1 , a 2 , ⋯ a n } \{a_1, a_2, \cdots a_n\} {a1,a2,⋯an} 和 { b 1 , b 2 , ⋯ b m } \{b…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
RFID推动新能源汽车零部件生产系统管理应用案例
RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域,电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式,存在单点位单独头溯源、网关布线…...
