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

news2024/11/14 2:49:45

解决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
     */
    @Bean
    public Queue deadQueue(){
        return new Queue("dead_queue");
    }
    /**
     * 死信队列交换机
     * @return
     */
    @Bean
    public DirectExchange deadExchange(){
        return new DirectExchange("dead.exchange");
    }

    /**
     * 死信队列和死信交换机绑定
     * @return
     */
    @Bean
    public Binding deadBinding(){
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");
    }
    
    /**
     * 普通队列
     * @return
     */
    @Bean
    public 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
     */
    @Bean
    public DirectExchange normalExchange(){
        return new DirectExchange("normal.exchange");
    }

    /**
     * 普通队列和普通交换机绑定
     * @return
     */
    @Bean
    public 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);

    @RabbitHandler
    public 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);

    @RabbitHandler
    public 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);
    @Autowired
    private 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有偏差(应该是在普通队列中处理超时的一种补偿机制,如果只监听死信队列,那就完全不需要在配置时普通队列里面定义死信队列,虽然这种做法可以解决业务问题),另一方面官方也有提到:

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

示例代码如下:

@Configuration
public class MQConfig {
    /**
     * 死信队列
     * @return
     */
    @Bean
    public Queue deadQueue(){
        return new Queue("dead_queue");
    }
    /**
     * 死信队列交换机
     * @return
     */
    @Bean
    public DirectExchange deadExchange(){
        return new DirectExchange("dead.exchange");
    }

    /**
     * 死信队列和死信交换机绑定
     * @return
     */
    @Bean
    public Binding deadBinding(){
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");
    }
    
    /**
     * 普通队列
     * @return
     */
    @Bean
    public 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
     */
    @Bean
    public DirectExchange normalExchange(){
        return new DirectExchange("normal.exchange");
    }

    /**
     * 普通队列和普通交换机绑定
     * @return
     */
    @Bean
    public 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);

    @RabbitHandler
    public 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);
    @Autowired
    private RabbitTemplate template;

    public void send() throws UnsupportedEncodingException {
        String msg = "hello world";
        log.info("发送消息:"+msg);
        template.convertAndSend("normal.exchange", "normal", msg);
    }
}

调用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);
    @RabbitHandler
    public 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);

    @RabbitHandler
    public void receive(String msg) 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);
//        }
    }
}

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

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

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

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

相关文章

【YashanDB知识库】archivelog磁盘满导致数据库abnormal

本文转自YashanDB官网,具体内容可见archivelog磁盘满导致数据库abnormal 【问题分类】功能使用 【关键字】磁盘空间满,archivelog日志,archivelog自动清理 【问题描述】数据库状态变更为abnormal,检查V$DIAG_INCIDENT视图&#…

足底筋膜炎5天自愈方法

足底筋膜炎并没有确切的5天自愈方法。足底筋膜炎是足底筋膜的一种无菌性炎症,主要症状是脚后跟部位的疼痛,这种疼痛通常是由于长时间站立、行走或跑步等引起的足底筋膜过度牵拉所致。由于这是一种慢性炎症,其恢复过程往往需要一定的时间&…

docker查看从当前最后100条起看日志

logs.sh 使用说明 logs.sh 是一个用于查看 Docker 容器日志的脚本。该脚本支持两种使用方式: 默认查看 video-console 容器的日志。通过指定容器 ID 来查看相应容器的日志。 1. 默认方式 不需要任何参数,直接运行脚本,将自动查找名为 vid…

百度副总裁陈洋:大模型让软件开发更高效、更安全

🎁👉点击进入文心快码 Baidu Comate 官网,体验智能编码之旅,还有超多福利!🎁 9月11日-12日,CCS 2024成都网络安全系列活动在成都举行。百度副总裁陈洋出席大会开幕式并进行主题分享。陈洋表示&a…

postgres_fdw访问存储在外部 PostgreSQL 服务器中的数据

文章目录 一、postgres_fdw 介绍二、安装使用示例三、成本估算四、 远程执行选项执行计划无法递推解决 参考文件: 一、postgres_fdw 介绍 postgres_fdw 模块提供外部数据包装器 postgres_fdw,可用于访问存储在外部 PostgreSQL 服务器中的数据。 此模块…

C语言代码练习(第二十三天)

今日练习: 65、有 n 个整数,使前面各数顺序向后移动 m 个位置,最后 m 个数变成最前面 m 个数,写一函数实现以上功能,在主函数中输入 n 个整数和输出调整后的 n 个数。(要求用指针) 66、 n 个人围…

JavaScript 基础 - 第17天_AJAX综合案例

文章目录 Day02_AJAX综合案例目录学习目标01.案例_图书管理-介绍目标讲解小结 02.Bootstrap 弹框_属性控制目标讲解小结 03.Bootstrap 弹框_JS控制目标讲解小结 04.案例_图书管理\_渲染列表目标讲解小结 05.案例_图书管理\_新增图书目标讲解小结 06.案例_图书管理\_删除图书目标…

网络编程Udp协议

文章目录 UDP协议1、什么是UDP协议?一、定义与基本概念二、主要特点三、报文格式四、应用场景五、总结 2、如何使用Java中的UDP套接字?一、UDP常用APIDatagramSocketDatagramPacket 二、UDP协议下的客户端-服务器服务器客户端 UDP协议 UDP协议&#xff…

电脑怎么录制视频?游戏直播、教学分享必备!

在数字化时代,电脑录屏已成为游戏直播、教学分享、会议记录等多种场景下的必备技能,但有些朋友可能不知道怎么高效又清晰的录制视频,下面就给大家汇总了几个简单方法,一起来学习下吧~ 1. 嗨格式录屏大师 录屏大师软件免费下载_高…

方位大模型教程:从基础入门到实战应用

2024年大西洋彼岸的OpenAi公司,首次向世界吹响「大模型主导未来世界变革」的号角。 AI大模型,正在构建的颠覆力,为了更好的入局AI大模型,这次我特意复盘和整理大模型学习脉络,开了30节大模型的课程,包含大…

推荐一个java屏幕共享项目

java 屏幕共享项目 https://github.com/SpringStudent/JavaDesktopShare

【JAVA入门】Day44 - 字节打印流和字符打印流

【JAVA入门】Day44 - 字节打印流和字符打印流 文章目录 【JAVA入门】Day44 - 字节打印流和字符打印流一、字节打印流二、字符打印流三、输出语句和打印流的关系 打印流也是一种高级包装流,但是它只有输出,没有输入。 打印流一般是指:…

Qt_控件的QWidget属性介绍

目录 1、QWidget的核心属性 2、enabled 3、geometry 3.1 代码测试geometry 4、windowTitle 4.1 代码测试windowTitle 5、windowIcon 5.1 QIcon设置图标 5.2 qrc机制 5.3 代码测试windowIcon 6、windowOpacity 6.1 代码测试windowOpacity 7、cursor 7.1 代码测试…

使用xjar+exe4j+inno setup把加密后的jar打包成exe应用程序并创建服务

1、使用xjar对jar包进行加密 在项目的pom.xml中加入xjar的插件依赖&#xff0c;最好用2.x.x版本&#xff0c;高版本不支持-Dxjar.mode&#xff0c;无法免密码启动 github地址&#xff1a;xjar-maven-plugin <plugin><groupId>com.github.core-lib</groupId>…

RDD2022 道路瑕疵检测数据集

RDD2022 道路瑕疵数据集 txt标签或者xml标签 一共23767张图片 D00 D01 D20 D40四类 D00纵向裂缝 D10横向裂缝 D20网状裂缝 D40坑洞。 RDD2022 道路瑕疵检测数据集介绍 数据集概述 RDD2022&#xff08;Road Defect Detection 2022&#xff09;是一个专门用于道路瑕疵检测的数…

【MATLAB GUI 设计第一篇 】

文章目录 前言一、MATLAB GUI 是什么&#xff1f;二、guide 的使用1.进入GUI界面2. 布置绘图3.修改字体4. 回调函数&#xff0c;完成功能5. 整个函数和回调函数6.修改回调函数7. 显示效果8. 补充 三、 APP 的使用1.进入APP界面2.在 APP 菜单下 新建空白APP3.创建回调函数4.显示…

OpenCV class2-C#+winfrom显示控件使用窗口大小并内存管理

一.控件效果说明 二.代码声明&#xff08;已经循环读取10000次&#xff09; 全局 OpenCvSharp.Point point new OpenCvSharp.Point(0, 0); OpenCvSharp.Size size2; Mat src new Mat(); 初始化 size2 new OpenCvSharp.Size(pictureBox1.Size.Width, pictureBox1.Size.Hei…

京东技术专家的修炼之道|“六边形战士”周默分享

前言 最近&#xff0c;京东零售智能增长研发总监周默在京东内部分享了他在京东工作近10年的经验与成长。周默自2015年加入京东以来&#xff0c;经历了后端架构、大数据、基础算法和业务算法等多个技术领域&#xff0c;获得了集团金项奖、最美京东人、集团算法大牛等多项荣誉&a…

C语言错题本

本学渣在一个人学习的途中遇到过许多问题 如果没有老师的帮助可能很长时间都走不出来 所以我也希望我的错误能帮助到你 9月13日 题一 未赋值的局部指针是野指针(C) 题二 PS:对于指针来说&#xff0c;传递的是指针值的副本&#xff0c;point 函数内部对 p 的任何修改都不会影响…