RabbitMQ死信延时队列阻塞问题
SHTL 博客:https://www.shtlls.ltd
问题
-
死信队列这种处理方式会出现一个问题:
由于是一个队列,遵循先进先出原则,且每次检查只会判断第一个消息是否过期,不会每一个都判断,所以会出现长时间过期的消息会阻塞短时间过期的消息的情况,也就无法实现同一队列中多种超时时间间隔延时执行。
这种方式也仅适用于过期时间一致的队列。
解决方法
可以根据不同的过期时间,设置不同的消息队列。
这个也仅针对过期时间类型仅为几种的情况,若间隔时间每一个都不一样,且无规律,则无法通过rabbitmq的死信队列实现,需要使用其他的方式。
-
(针对实际项目<实习生管理系统>解决方法)目前有两种时间类型,一种是24小时,一种是48小时
所以需要创建四个不同的延时队列,分别对应 22小时(79200000毫秒)、24小时(86400000毫秒)、46小时(165600000毫秒)、48小时(172800000毫秒)。
将不同时间的消息存入不同的消息队列,将四个消息队列的过期消息都转发到一个死信队列,消费者只需要监听该死信队列即可。
代码简述
-
依赖
<!-- spring-boot-starter-amqp --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> <version>2.6.1</version> </dependency>
-
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; }); } }
-
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小时,过期提醒 } }
-
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); } } }
图例
-
Exchanges
-
Queues
().equals(InterviewEnum.SECOND_INTERVIEW.getDesc())) {
secondInterviewService.sendRemindMail(bo);
}
}
}
```
图例
-
Exchanges
[外链图片转存中…(img-FB98151d-1658905164202)]
-
Queues
[外链图片转存中…(img-SDSa9FJD-1658905164204)]