RabbitMQ死信延时队列阻塞问题

本文介绍了如何避免RabbitMQ死信队列中消息阻塞,通过为不同过期时间创建独立队列并配置死信处理,确保不同类型超时消息的及时执行。针对实习管理系统中的24小时和48小时过期需求,设计了四个定制队列及一个死信队列的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RabbitMQ死信延时队列阻塞问题

SHTL 博客:https://www.shtlls.ltd

问题
  1. 死信队列这种处理方式会出现一个问题:

    ​ 由于是一个队列,遵循先进先出原则,且每次检查只会判断第一个消息是否过期,不会每一个都判断,所以会出现长时间过期的消息会阻塞短时间过期的消息的情况,也就无法实现同一队列中多种超时时间间隔延时执行。

    ​ 这种方式也仅适用于过期时间一致的队列。

解决方法
可以根据不同的过期时间,设置不同的消息队列。
这个也仅针对过期时间类型仅为几种的情况,若间隔时间每一个都不一样,且无规律,则无法通过rabbitmq的死信队列实现,需要使用其他的方式。
  1. (针对实际项目<实习生管理系统>解决方法)目前有两种时间类型,一种是24小时,一种是48小时

    ​ 所以需要创建四个不同的延时队列,分别对应 22小时(79200000毫秒)、24小时(86400000毫秒)、46小时(165600000毫秒)、48小时(172800000毫秒)。

    ​ 将不同时间的消息存入不同的消息队列,将四个消息队列的过期消息都转发到一个死信队列,消费者只需要监听该死信队列即可。

代码简述
  1. 依赖

    <!-- spring-boot-starter-amqp -->
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-amqp</artifactId>
    	<version>2.6.1</version>
    </dependency>
    
  2. RabbitMqTtlConfig.java

    package com.macro.mall.config;
    
    import org.springframework.amqp.core.*;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class RabiitMqTtlConfig {
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
    
        @Bean("interviewTtlExchange")
        public DirectExchange ttlExchange() {
            return new DirectExchange("interview-ttl-exchange");
        }
    
        // 创建死信队列交换机 -direct类型
        @Bean("interviewDeadLetterExchange")
        public DirectExchange deadLetterExchange() {
            return new DirectExchange("interview-dead-letter-exchange");
        }
    
    
         @Bean("interviewTtlQueue22")
        public Queue ttlQueue22() {
            //设置在这个队列上的私信的去处->
            Map<String, Object> args = new HashMap<>(2);
            args.put("x-dead-letter-exchange", "interview-dead-letter-exchange");
            args.put("x-dead-letter-routing-key", "interview.dead.letter");
    
            //过期时间由队列统一设置
            //注意不是 x-expires,x-expires为队列存活时间,
            // x-message-ttl为队列内的消息存活时间
            //注意更改队列/交换机设置需要删除原有的
            return new Queue("interview-ttl-queue-22", true, false, false, args);
        }
    
        @Bean("interviewTtlQueue24")
        public Queue ttlQueue24() {
            //设置在这个队列上的私信的去处->
            Map<String, Object> args = new HashMap<>(2);
            args.put("x-dead-letter-exchange", "interview-dead-letter-exchange");
            args.put("x-dead-letter-routing-key", "interview.dead.letter");
            //过期时间由队列统一设置
            //注意不是 x-expires,x-expires为队列存活时间,
            // x-message-ttl为队列内的消息存活时间
            //注意更改队列/交换机设置需要删除原有的
            return new Queue("interview-ttl-queue-24", true, false, false, args);
        }
    
        @Bean("interviewTtlQueue46")
        public Queue ttlQueue46() {
            //设置在这个队列上的私信的去处->
            Map<String, Object> args = new HashMap<>(2);
            args.put("x-dead-letter-exchange", "interview-dead-letter-exchange");
            args.put("x-dead-letter-routing-key", "interview.dead.letter");
    
            //过期时间由队列统一设置
            //注意不是 x-expires,x-expires为队列存活时间,
            // x-message-ttl为队列内的消息存活时间
            //注意更改队列/交换机设置需要删除原有的
            return new Queue("interview-ttl-queue-46", true, false, false, args);
        }
    
        @Bean("interviewTtlQueue48")
        public Queue ttlQueue48() {
            //设置在这个队列上的私信的去处->
            Map<String, Object> args = new HashMap<>(2);
            args.put("x-dead-letter-exchange", "interview-dead-letter-exchange");
            args.put("x-dead-letter-routing-key", "interview.dead.letter");
    
            //过期时间由队列统一设置
            //注意不是 x-expires,x-expires为队列存活时间,
            // x-message-ttl为队列内的消息存活时间
            //注意更改队列/交换机设置需要删除原有的
            return new Queue("interview-ttl-queue-48", true, false, false, args);
        }
    
    
        // 绑定交换机与消息队列
        @Bean
        @DependsOn({"interviewTtlExchange", "interviewTtlQueue22"})
        public Binding ttlBinding22(@Qualifier("interviewTtlQueue22") Queue interviewTtlQueue22, Exchange interviewTtlExchange) {
            return BindingBuilder.bind(interviewTtlQueue22).to(interviewTtlExchange).with("interview.ttl.22").noargs();
        }
    
        @Bean
        @DependsOn({"interviewTtlExchange", "interviewTtlQueue24"})
        public Binding ttlBinding24(@Qualifier("interviewTtlQueue24") Queue interviewTtlQueue24, Exchange interviewTtlExchange) {
            return BindingBuilder.bind(interviewTtlQueue24).to(interviewTtlExchange).with("interview.ttl.24").noargs();
        }
    
        @Bean
        @DependsOn({"interviewTtlExchange", "interviewTtlQueue46"})
        public Binding ttlBinding46(@Qualifier("interviewTtlQueue46") Queue interviewTtlQueue46, Exchange interviewTtlExchange) {
            return BindingBuilder.bind(interviewTtlQueue46).to(interviewTtlExchange).with("interview.ttl.46").noargs();
        }
    
        @Bean
        @DependsOn({"interviewTtlExchange", "interviewTtlQueue48"})
        public Binding ttlBinding48(@Qualifier("interviewTtlQueue48") Queue interviewTtlQueue48, Exchange interviewTtlExchange) {
            return BindingBuilder.bind(interviewTtlQueue48).to(interviewTtlExchange).with("interview.ttl.48").noargs();
        }
    
        /**
         * 给22小时后过期消息队列发送消息
         */
        public void send22(String msg) {
            rabbitTemplate.convertAndSend("interview-ttl-exchange", "interview.ttl.22", msg, message -> {
                // 设置过期时间,过期之后从interview-ttl-queue死信转到interview-dead-letter-queue
                // 22小时后过期
                message.getMessageProperties().setExpiration("79200000");
                return message;
            });
        }
    
        /**
         * 给24小时后过期消息队列发送消息
         */
        public void send24(String msg) {
            rabbitTemplate.convertAndSend("interview-ttl-exchange", "interview.ttl.24", msg, message -> {
                // 设置过期时间,过期之后从interview-ttl-queue死信转到interview-dead-letter-queue
                // 24小时后过期
                message.getMessageProperties().setExpiration("86400000");
                return message;
            });
        }
    
        /**
         * 给46小时后过期消息队列发送消息
         */
        public void send46(String msg) {
            System.err.println("46\t" + msg);
            rabbitTemplate.convertAndSend("interview-ttl-exchange", "interview.ttl.46", msg, message -> {
                // 设置过期时间,过期之后从interview-ttl-queue死信转到interview-dead-letter-queue
                // 46小时后过期  165600000
                message.getMessageProperties().setExpiration("165600000");
                return message;
            });
        }
    
        /**
         * 给48小时后过期消息队列发送消息
         */
        public void send48(String msg) {
            System.err.println("48\t" + msg);
            rabbitTemplate.convertAndSend("interview-ttl-exchange", "interview.ttl.48", msg, message -> {
                // 设置过期时间,过期之后从interview-ttl-queue死信转到interview-dead-letter-queue
                // 48小时后过期   172800000
                message.getMessageProperties().setExpiration("172800000");
                return message;
            });
        }
    
    }
    
  3. RabbitMqSendUtils.java

    package com.macro.mall.utils;
    
    import com.alibaba.fastjson.JSON;
    import com.macro.mall.bo.InterviewSaveMqBO;
    import com.macro.mall.common.enums.ExpireEnum;
    import com.macro.mall.config.RabiitMqTtlConfig;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class RabbitMqSendUtils {
    
        @Autowired
        private RabiitMqTtlConfig rabiitMqTtlConfig;
    
        /**
         * 初筛过期定时提醒
         * @param bo
         */
        public void primaryScreeningSend(InterviewSaveMqBO bo) {
            bo.setExpire(ExpireEnum.ABOUT_TO_EXPIRE.getNum());
            rabiitMqTtlConfig.send22(JSON.toJSONString(bo));    // 设置22小时,过期前2小时提醒
            bo.setExpire(ExpireEnum.EXPIRED.getNum());
            rabiitMqTtlConfig.send24(JSON.toJSONString(bo));    // 设置24小时,过期提醒
        }
    
        /**
         * 一面、二面过期定时提醒
         * @param bo
         */
        public void firstSecondInterviewSend(InterviewSaveMqBO bo) {
            bo.setExpire(ExpireEnum.BIGIN_TO_INTERVIEW.getNum());
            System.out.println(JSON.toJSONString(bo));
            bo.setExpire(ExpireEnum.ABOUT_TO_EXPIRE.getNum());
            rabiitMqTtlConfig.send46(JSON.toJSONString(bo));    // 设置46小时,过期前2小时提醒
            bo.setExpire(ExpireEnum.EXPIRED.getNum());
            rabiitMqTtlConfig.send48(JSON.toJSONString(bo));    // 设置48小时,过期提醒
        }
    }
    
  4. DeadLetterListener.java

    package com.macro.mall.config;
    
    import com.alibaba.fastjson.JSON;
    import com.macro.mall.bo.InterviewSaveMqBO;
    import com.macro.mall.common.enums.InterviewEnum;
    import com.macro.mall.service.FirstInterviewService;
    import com.macro.mall.service.PrimaryScreeningService;
    import com.macro.mall.service.SecondInterviewService;
    import org.springframework.amqp.rabbit.annotation.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * 监听RabbitMQ队列,处理定时邮件通知
     */
    @Component
    public class DeadLetterListener {
    
        @Autowired
        private PrimaryScreeningService primaryScreeningService;
        @Autowired
        private FirstInterviewService firstInterviewService;
        @Autowired
        private SecondInterviewService secondInterviewService;
    
        @RabbitHandler
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue("interview-dead-letter-queue"),
                exchange = @Exchange("interview-dead-letter-exchange"),
                key = "interview.dead.letter" ))
        public void onMessage(String msg) {
            InterviewSaveMqBO bo = JSON.parseObject(msg,InterviewSaveMqBO.class);
            // 以下可以对返回的消息进行处理了
            if (bo.getDesc().equals(InterviewEnum.PRIMARY_SCREENING.getDesc())) {
                primaryScreeningService.sendRemindMail(bo);
            } else if (bo.getDesc().equals(InterviewEnum.FIRST_INTERVIEW.getDesc())) {
                firstInterviewService.sendRemindMail(bo);
            } else if (bo.getDesc().equals(InterviewEnum.SECOND_INTERVIEW.getDesc())) {
                secondInterviewService.sendRemindMail(bo);
            }
        }
    
    }
    
图例
  1. Exchanges

    image-20211224140450451

  2. Queues

    image-20211224140513487

().equals(InterviewEnum.SECOND_INTERVIEW.getDesc())) {
secondInterviewService.sendRemindMail(bo);
}
}

  }
  ```
图例
  1. Exchanges

    [外链图片转存中…(img-FB98151d-1658905164202)]

  2. Queues

    [外链图片转存中…(img-SDSa9FJD-1658905164204)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值