解决RabbitMQ设置TTL过期后不进入死信队列

解决RabbitMQ设置TTL过期后不进入死信队列

  • 问题发现
  • 问题解决
    • 方法一:手动拒绝消息,并且重新放回队列中
    • 方法二:改为自动确认模式
    • 方法三:只监听死信队列,在死信队列里面处理业务逻辑

问题发现

最近再学习RabbitMQ过程中,看到关于死信队列内容:

来自队列的消息可以是 “死信”,这意味着当以下四个事件中的任何一个发生时,这些消息将被重新发布到 Exchange

  1. 使用 basic.rejectbasic.nackrequeue 参数设置为 false 的使用者否定该消息
  2. 消息由于每条消息的 TTL 而过期
  3. 队列超出了长度限制
  4. 消息返回到 quorum 队列的次数超过了 delivery-limit 的次数。

再模拟TTL过期时遇到的疑惑,特此记录下来,示例代码如下:
先设置为手动应答模式:

#手动应答
spring.rabbitmq.listener.simple.acknowledge-mode = manual

绑定队列,示例代码如下:

@Configuration
public class MQConfig {/*** 死信队列* @return*/@Beanpublic Queue deadQueue(){return new Queue("dead_queue");}/*** 死信队列交换机* @return*/@Beanpublic DirectExchange deadExchange(){return new DirectExchange("dead.exchange");}/*** 死信队列和死信交换机绑定* @return*/@Beanpublic Binding deadBinding(){return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");}/*** 普通队列* @return*/@Beanpublic Queue queue(){
//          方法一  
//        Queue normalQueue = new Queue("normal_queue");
//        normalQueue.addArgument("x-dead-letter-exchange", "dead.exchange"); // 死信队列
//        normalQueue.addArgument("x-dead-letter-routing-key", "dead"); // 死信队列routingKey
//        normalQueue.addArgument("x-message-ttl", 10000); // 死信队列routingKey
//        方法二return QueueBuilder.durable("normal_queue").deadLetterExchange("dead.exchange").deadLetterRoutingKey("dead").ttl(10000).build();}/*** 普通交换机* @return*/@Beanpublic DirectExchange normalExchange(){return new DirectExchange("normal.exchange");}/*** 普通队列和普通交换机绑定* @return*/@Beanpublic Binding binding(){return BindingBuilder.bind(queue()).to(normalExchange()).with("normal");}
}

监听普通队列消费方,示例代码如下:

@Component
@RabbitListener(queues = "normal_queue")
public class MQReceiver {private static final Logger log = LoggerFactory.getLogger(MQReceiver.class);@RabbitHandlerpublic void receive(String msg, Message message, Channel channel) throws IOException, InterruptedException {log.info("收到消息:"+msg);}
}

监听死信队列消费方,示例代码如下:

@Component
@RabbitListener(queues = "dead_queue")
public class MQReceiver2 {private static final Logger log = LoggerFactory.getLogger(MQReceiver2.class);@RabbitHandlerpublic void receive(String msg, Message message, Channel channel) throws IOException {log.info("死信队列收到消息:{}",msg);// 参数一:当前消息标签,参数二:true该条消息已经之前所有未消费设置为已消费,false只确认当前消息channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}
}

发送方,向普通队列发送消息,示例代码如下:

@Component
public class MQSender {private static final Logger log = LoggerFactory.getLogger(MQSender.class);@Autowiredprivate RabbitTemplate template;public void send() throws UnsupportedEncodingException {String msg = "hello world";log.info("发送消息:"+msg);template.convertAndSend("normal.exchange", "normal", msg);}
}

执行结果如图:

在这里插入图片描述
时间到了后,死信队列长时间未收到消息,消息一直在普通队列中,如图所示:

在这里插入图片描述

然后开始百度,网上很多都说什么配置不对啥的,还有说队列的预取值太大导致的问题(扯犊子呢),反正就是没有找到一个合理的解释。

然后吃了个饭回来,发现RabbitMQ报了一个长时间未收到消息确认的错误(大概意思就是说ACK消息确认超时时间为18000毫秒也就是30分钟),原来RabbitMQ一直在等待消息确认,所以一直被持有,当普通队列挂了(重启后),被释放,进入死信队列。

PRECONDITION_FAILED - delivery acknowledgement on channel 1 timed out. Timeout value used: 1800000 ms. This timeout value can be configured, see consumers doc guide to learn more

在这里插入图片描述
这下知道为什么不进入死信队列的原因了。新的问题又来了,如果我手动确认或者拒绝了,那不就达不到TTL过期的效果了吗?

问题解决

先看一下页面上是如何操作的,创建一个TTL为10s的普通队列并且绑定了死信队列,如图所示:
在这里插入图片描述
我们向普通交换机里面送送一条消息然后查看它的状态,如图所示:

在这里插入图片描述
当消息发送后,等待10s查看是否自动进入死信队列,如图(GIF)所示:

在这里插入图片描述
我们发现当消息进入普通队列后,并且没有被消费的情况下(或者没有被消费者持有的情况下),10s后会自动进入死信队列。

方法一:手动拒绝消息,并且重新放回队列中

基于上面的思想,手动调用basicReject()方法,将requeue参数设置为true,重新放回队列中,那么时间到了就会进入死信队列。

先开启手动确认模式,示例代码如下:

spring.rabbitmq.listener.simple.acknowledge-mode = manual

发送方代码和配置的代码就不重复展示了(参考之前示例),消费方示例代码如下:

@Component
@RabbitListener(queues = "normal_queue")
public class MQReceiver {private static final Logger log = LoggerFactory.getLogger(MQReceiver.class);@RabbitHandlerpublic void receive(String msg,Message message,Channel channel) throws IOException {log.info("收到消息:"+msg);channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);}
}
@Component
@RabbitListener(queues = "dead_queue")
public class MQReceiver2 {private static final Logger log = LoggerFactory.getLogger(MQReceiver2.class);@RabbitHandlerpublic void receive(String msg,Message message,Channel channel) throws IOException {log.info("死信队列收到消息:{}",msg);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}
}

调用send()方法,执行结果如图:

在这里插入图片描述
再等待过期的过程中,会重复发送很多条消息,但是我们可以看到第一次发现消息的时间和进入死信队列的时间正好间隔10s。

方法二:改为自动确认模式

另外一种方法,我们在普通队列里面,模拟业务出现异常情况(如果只是单纯模拟业务超时,不会进入死信队列(此时被消费者持有状态),直接就确认消费了)。

我们先把手动确认的配置删除或者修改为自动确认,示例代码如下:

#spring.rabbitmq.listener.simple.acknowledge-mode = auto

发送方代码和配置的代码就不重复展示了(参考之前示例),消费方示例代码如下:

@Component
@RabbitListener(queues = "normal_queue")
public class MQReceiver {private static final Logger log = LoggerFactory.getLogger(MQReceiver.class);@RabbitHandlerpublic void receive(String msg) throws IOException, InterruptedException {log.info("收到消息:"+msg);throw new RuntimeException();}
}
@Component
@RabbitListener(queues = "dead_queue")
public class MQReceiver2 {private static final Logger log = LoggerFactory.getLogger(MQReceiver2.class);@RabbitHandlerpublic void receive(String msg) throws IOException {log.info("死信队列收到消息:{}",msg);}
}

调用send()方法,执行结果如图:

在这里插入图片描述
我们可以看到第一次进入普通队列时间和最后一次报错进入死信队列的时间,正好间隔10s。但是这中间会重复发起N次,如果TTL时长越大,可能会导致资源消耗过高,但这又属于另外一个问题了。

方法三:只监听死信队列,在死信队列里面处理业务逻辑

这个方法是参考众多文章比较常见的一个做法,但是个人感觉与我理解的TTL有偏差(应该是在普通队列中处理超时的一种补偿机制,如果只监听死信队列,那就完全不需要在配置时普通队列里面定义死信队列,虽然这种做法可以解决业务问题,但是放在延迟任务里讲解会比较合适),另一方面官方也有提到:

消息可以在写入套接字之后过期,但在到达消费者之前过期。

示例代码如下:

@Configuration
public class MQConfig {/*** 死信队列* @return*/@Beanpublic Queue deadQueue(){return new Queue("dead_queue");}/*** 死信队列交换机* @return*/@Beanpublic DirectExchange deadExchange(){return new DirectExchange("dead.exchange");}/*** 死信队列和死信交换机绑定* @return*/@Beanpublic Binding deadBinding(){return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");}/*** 普通队列* @return*/@Beanpublic Queue queue(){
//          方法一  
//        Queue normalQueue = new Queue("normal_queue");
//        normalQueue.addArgument("x-dead-letter-exchange", "dead.exchange"); // 死信队列
//        normalQueue.addArgument("x-dead-letter-routing-key", "dead"); // 死信队列routingKey
//        normalQueue.addArgument("x-message-ttl", 10000); // 死信队列routingKey
//        方法二return QueueBuilder.durable("normal_queue").deadLetterExchange("dead.exchange").deadLetterRoutingKey("dead").ttl(10000).build();}/*** 普通交换机* @return*/@Beanpublic DirectExchange normalExchange(){return new DirectExchange("normal.exchange");}/*** 普通队列和普通交换机绑定* @return*/@Beanpublic Binding binding(){return BindingBuilder.bind(queue()).to(normalExchange()).with("normal");}}

消费方只监听死信队列:

@Component
@RabbitListener(queues = "dead_queue")
public class MQReceiver2 {private static final Logger log = LoggerFactory.getLogger(MQReceiver2.class);@RabbitHandlerpublic void receive(String msg, Message message, Channel channel) throws IOException {log.info("死信队列收到消息:{}",msg);// 伪代码:判断订单状态,1支付成功,2支付超时
//        if(order.state == 1){
//            // 参数一:当前消息标签,参数二:true该条消息已经之前所有未消费设置为已消费,false只确认当前消息
//            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//        }else{
//            // todo 修改订单状态
//            channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
//        }}
}

发送方代码如下:

@Component
public class MQSender {private static final Logger log = LoggerFactory.getLogger(MQSender.class);@Autowiredprivate RabbitTemplate template;public void send() throws UnsupportedEncodingException {String msg = "hello world";log.info("发送消息:"+msg);template.convertAndSend("normal.exchange", "normal", msg);}
}

调用send()方法,执行结果如图:

在这里插入图片描述
可以看到从发送时间到进入死信队列时间正好间隔10s。

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

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

相关文章

排序-----选择排序

首先介绍几种排序的分类: 选择排序是每次都遍历,标记出最小的元素,然后把它放在前面。 本文介绍优化后的版本:每次遍历标记出最小的和最大的元素,分别放到前面和后面。(注意这里是找到对应的下标&#xff0…

Unity Transform 组件

在 Unity 中,Transform 是一个非常重要的组件,它定义了物体的位置、旋转和缩放,几乎每个 GameObject 都包含一个 Transform 组件。Transform 组件的主要属性如下: 1. position 表示物体在世界空间中的位置。可以通过 transf…

【西电电装实习】6. 手装无人机的蓝牙断连debug

文章目录 前言零、闪灯状态零零、翻滚角,俯仰角,偏航角一、问题描述二、现象解释三、解决方案参考文献 前言 在 西电无人机电装实习 时遇到的问题使用蓝牙芯片 CH582F。沁恒的蓝牙芯片CH582F是一款集成了BLE(Bluetooth Low Energy&#xff0…

Unity制作角色溶解变成光点消失

Unity制作角色溶解变成光点消失 大家好,我是阿赵。   在很多游戏里面,角色死亡之后都会有一些特殊的消失方式。这里我也来做一种,角色溶解成光点消失的效果。 我还是随便拿了Unity的资源商店的免费资源来使用。不过由于这个角色自带没有死…

【楚怡杯】职业院校技能大赛 “云计算应用” 赛项样题六

某企业根据自身业务需求,实施数字化转型,规划和建设数字化平台,平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”,拟采用开源OpenStack搭建企业内部私有云平台,开源Kubernetes搭建云原生服务平台,选…

JVM字节码与局部变量表

文章目录 局部变量表javap字节码指令分类 指令指令数据类型前缀加载和存储指令加载常量算术指令其他指令 字节码示例说明 局部变量表 每个线程的帧栈是独立的,每个线程中的方法调用会产生栈帧,栈帧中保存着方法执行的信息,例如局部变量表。 …

防火墙配置变更管理

在任何组织中,当涉及到网络安全时,频繁地更换防火墙是必要的,实施简化的防火墙更改管理策略模板可以减少管理时间,还可以减少每次变更引入新的安全性或合规性问题的可能性。典型的防火墙变更管理流程将包括以下步骤: …

八股文-多线程、并发

八股文-多线程、并发 最近学到了一种方法,可以用于简历项目经验编写以及面试题目的回答 STAR法则:在什么背景下,你需要解决什么问题,你做了啥,得到了什么结果 情境(Situation): 描…

【裸机装机系列】4.kali(ubuntu)-配置个人用户的sudo权限并进行bashrc的其他配置

前面更新好软件之后,就正式进入使用环节了,不过先不要着急,还需要配置一些东西才能使用 文中提到的: 自己账户内——frank㉿practice root账户内——root㉿practice其中 ㉿ 前 是用户名 ㉿ 后 是主机名 也就是在前面U盘装机的时…

动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?

JavaScript 被称为动态语言,而 Java 被称为静态语言 这主要与它们在类型系统、编译执行方式以及运行时行为等方面的不同特性有关。详细差异如下: JavaScript (动态语言) 动态类型: 在JavaScript中,变量的类型是在运行时确定的。这…

【数据结构与算法】LeetCode:双指针法

文章目录 LeetCode:双指针法正序同向而行(快慢指针)移除元素移动零(Hot 100)删除有序数组中的重复项颜色分类(Hot 100)压缩字符串移除链表元素删除排序链表中的重复元素删除排序链表中的重复元素…

声通科技子公司金讯数智,引领热线创新与治理新篇章

6月28日,“热线发展与治理创新”年度论坛在美丽的海滨城市厦门圆满落幕。此次盛会汇聚了政府、高校及企业界的精英,共同探讨人工智能如何赋能政府治理与政务服务创新,其中,声通科技子公司西安金讯数智信息技术有限公司&#xff08…

无人机维修保养一对一教学技术详解

随着无人机技术的日益普及和应用的广泛深入,无人机的维修保养成为确保飞行安全、延长使用寿命的关键环节。为了培养专业的无人机维护人才,一对一教学成为了一种高效、针对性的培训方式。以下将详细解析无人机维修保养一对一教学的技术要点,涵…

QT Layout布局,隐藏其中的某些部件后,不影响原来的布局

最近在工作时,被要求,需要将布局中的某些部件隐藏后,但不能影响原来的布局。 现在记录解决方案! 一、水平布局(垂直布局一样) ui中的布局 效果: 按钮可以任意隐藏,都不影响其中布…

Ceph 基本架构(一)

Ceph架构图 Ceph整体组成 Ceph 是一个开源的分布式存储系统,设计用于提供优秀的性能、可靠性和可扩展性。Ceph 的架构主要由几个核心组件构成,每个组件都有特定的功能,共同协作以实现高可用性和数据的一致性。 以下是 Ceph 的整体架构及其…

Pikachu靶场之XSS

先来点鸡汤,少就是多,慢就是快。 环境搭建 攻击机kali 192.168.146.140 靶机win7 192.168.146.161 下载zip,pikachu - GitCode 把下载好的pikachu-master,拖进win7,用phpstudy打开网站根目录,.....再用…

CleanMyMac 5 for Mac 最新中文破解版下载 系统优化垃圾清理工具

今天给大家带来的是CleanMyMac最新款CleanMyMac 5,它是一个全面的Mac清理和维护工具,通过提供多项强大的功能,帮助用户简化日常维护任务,提升系统性能,同时保护个人隐私和安全。无论是新手还是经验丰富的Mac用户&#…

【SpringBoot】调度和执行定时任务--Quartz(超详细)

Quartz 是一个功能强大的任务调度框架,广泛用于在 Java 应用程序中定时执行任务,同时它支持 Cron 表达式、持久化任务、集群等特性。以下是 Quartz 的详细使用教程,包括安装、基本概念、简单示例和高级功能。 1. 安装 Quartz 首先&#xff…

京东广告投放平台整洁架构演进之路

作者:广告研发 赵嘉铎 前言 从去年开始京东广告投放系统做了一次以领域驱动设计为思想内核的架构升级,在深入理解DDD思想的同时,我们基于广告投放业务的本质特征大胆地融入了自己的理解和改造。新架构是从设计思想到落地框架都进行了彻底的…

Python 解析 Charles JSON Session File (.chlsj)

Charles 代理,是一款抓包软件,可以帮助我们抓取浏览器请求跟响应。 1、在 Filter 里面输入需要抓包的网址 2、右键 Export Session 3、文件类型选择 JSON Session File (.chlsj) 保存 4、解析响应的数据结构 response.body.text 是文本字符串。 # 导入…