本文主要用使用Spring Boot(2.5.2)来整合RabbitMQ(2.5.2),使用的是simple容器实现的消费者。本文的前提是有一个安装好的RabbitMQ的环境,及我的上一篇文章里生产者服务:
链接: RabbitMQ笔记(一)SpringBoot整合RabbitMQ之simple容器(消费者)
链接: RabbitMQ笔记(二)SpringBoot整合RabbitMQ之simple容器(生产者)
链接: RabbitMQ笔记(三)RabbitMQ持久化的几个姿势(Spring Boot版本)
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.2version>
<relativePath/>
parent>
<groupId>com.aliangroupId>
<artifactId>dlqartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>dlqname>
<description>SpringBoot整合RabbitMQ之死信队列description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.68version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.10version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatypegroupId>
<artifactId>jackson-datatype-jsr310artifactId>
<version>2.9.10version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.16version>
dependency>
<dependency>
<groupId>com.aliangroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
这里需要注意的是下面这个包,是我本人打包到私服的,本文中主要用到一个查询包装类,加上一个常量类,在我上几篇文章里也提过,就不多说了。
<dependency>
<groupId>com.aliangroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
这里也给个打包的命令:
call mvn clean source:jar deploy -Dmaven.test.skip=true
这个配置类和我之前文章RabbitMQ笔记(一)SpringBoot整合RabbitMQ之simple容器(消费者)讲的是一样的,直接拷贝过来的,只不过我为了方便测试,把消息应答模式改为了自动应答(AcknowledgeMode.AUTO)
SimpleRabbitMqConfig.java
package com.alian.dlq.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class SimpleRabbitMqConfig {
/**
* SimpleMessageListenerContainer
*
* @param connectionFactory
* @return
*/
@Bean(name = "simpleContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory simpleContainerFactory = new SimpleRabbitListenerContainerFactory();
//设置连接工厂
simpleContainerFactory.setConnectionFactory(connectionFactory);
//接收消息采用Jackson2JsonMessageConverter序列化
simpleContainerFactory.setMessageConverter(this.jackson2JsonMessageConverter());
//设置初始消费者数量(SimpleRabbitListenerContainerFactory配置类的配置优先级比配置文件高)
simpleContainerFactory.setConcurrentConsumers(2);
//设置最大消费者数量(SimpleRabbitListenerContainerFactory配置类的配置优先级比配置文件高)
simpleContainerFactory.setMaxConcurrentConsumers(10);
//设置消费者每次获取的消息数,默认250(SimpleRabbitListenerContainerFactory配置类的配置优先级比配置文件高)
simpleContainerFactory.setPrefetchCount(30);
//应答模式NONE:不确认模式,MANUAL:手动确认模式,AUTO:自动确认模式
simpleContainerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);
//消费者listener抛出异常,是否重回队列,默认true:重回队列, false为不重回队列(结合死信交换机)
simpleContainerFactory.setDefaultRequeueRejected(false);
return simpleContainerFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
//设置连接工厂
rabbitTemplate.setConnectionFactory(connectionFactory);
//接收消息采用Jackson2JsonMessageConverter序列化(支持java 8时间)
rabbitTemplate.setMessageConverter(this.jackson2JsonMessageConverter());
//Mandatory为true时,消息通过交换器无法匹配到队列会返回给生产者,为false时匹配不到会直接被丢弃
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
@Bean("jacksonMessageConverter")
public MessageConverter jackson2JsonMessageConverter() {
ObjectMapper mapper = getMapper();
return new Jackson2JsonMessageConverter(mapper);
}
/**
* 使用com.fasterxml.jackson.databind.ObjectMapper
* 对数据进行处理包括java8里的时间
*
* @return
*/
private ObjectMapper getMapper() {
ObjectMapper mapper = new ObjectMapper();
//设置可见性
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//默认键入对象
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//设置Java 8 时间序列化
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
//禁用把时间转为时间戳
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//遇到未知属性或者属性不匹配的时候不抛出异常
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(timeModule);
return mapper;
}
}
AppProperties.java
package com.alian.dlq.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@Component
@ConfigurationProperties(value = "app")
public class AppProperties {
/**
* 查询间隔
*/
private List<Integer> queryGrap;
/**
* 最大查询次数
*/
private int maxQueryCount;
}
MQConstants放到公共包里的原因就是多个系统都可以共用,比如我之前文章用到的也在这个里面,QueryDto放到公共包是因为消息反序列化问题,反序列化是要同一个包路径,否则就会发生异常(比如:在生产者里发送消息序列化的路径是“com.alian.common.dto.QueryDto”,而你在消费这服务里自己拷贝了一份QueryDto,然后包路径变成“com.alian.rabbitmq.dto.QueryDto”,这样就会出现异常),我之前的文章有详细的介绍。
MQConstants.java
package com.alian.common.constant;
public class MQConstants {
/**
* 交换机
*/
public final static String ALIAN_EXCHANGE_NAME = "ALIAN_EXCHANGE";
public final static String PT_EXCHANGE_NAME = "PT_EXCHANGE";
//死信交换机
public final static String PT_DELAY_EXCHANGE_NAME = "PT_DELAY_EXCHANGE";
/**
* 队列名
*/
public final static String ALIAN_QUEUE_NAME = "ALIAN_QUEUE";
public final static String OIS_QUEUE_NAME = "OIS_QUEUE";
//死信队列
public final static String OIS_DELAY_QUEUE_LEVEL1_NAME = "OIS_DELAY_QUEUE_LEVEL1";
public final static String OIS_DELAY_QUEUE_LEVEL2_NAME = "OIS_DELAY_QUEUE_LEVEL2";
public final static String OIS_DELAY_QUEUE_LEVEL3_NAME = "OIS_DELAY_QUEUE_LEVEL3";
public final static String OIS_DELAY_QUEUE_LEVEL4_NAME = "OIS_DELAY_QUEUE_LEVEL4";
public final static String OIS_DELAY_QUEUE_LEVEL5_NAME = "OIS_DELAY_QUEUE_LEVEL5";
/**
* 路由key
*/
public final static String ALIAN_ROUTINGKEY_NAME = "ALIAN_ROUTINGKEY";
public final static String OIS_ROUTINGKEY_NAME = "OIS_ROUTINGKEY";
//死信队列路由
public final static String OIS_DELAY_ROUTINGKEY_LEVEL1_NAME = "OIS_DELAY_ROUTINGKEY_LEVEL1";
public final static String OIS_DELAY_ROUTINGKEY_LEVEL2_NAME = "OIS_DELAY_ROUTINGKEY_LEVEL2";
public final static String OIS_DELAY_ROUTINGKEY_LEVEL3_NAME = "OIS_DELAY_ROUTINGKEY_LEVEL3";
public final static String OIS_DELAY_ROUTINGKEY_LEVEL4_NAME = "OIS_DELAY_ROUTINGKEY_LEVEL4";
public final static String OIS_DELAY_ROUTINGKEY_LEVEL5_NAME = "OIS_DELAY_ROUTINGKEY_LEVEL5";
}
QueryDto.java
package com.alian.common.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.Objects;
@Data
public class QueryDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 交易流水
*/
private String tranSeq = "";
/**
* 第几次查询
*/
private int queryCount = 0;
}
我这里只是定义了三个死信队列进行演示,你们可以根据自己的业务需要定义多个,注意不要绑定错误即可。
DeadLetterConfig.java
package com.alian.dlq.config;
import com.alian.common.constant.MQConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DeadLetterConfig {
public static Map<String, Object> queueParams = new HashMap<>();
/**
* 消息过期后,都发送到OIS_QUEUE
*/
static {
//消息在队列中存活的时间
queueParams.put("x-message-ttl", 8 * 60 * 60 * 1000);
//消息过期后要发送的交换机
queueParams.put("x-dead-letter-exchange", MQConstants.PT_EXCHANGE_NAME);
//消息过期后要发送的路由
queueParams.put("x-dead-letter-routing-key", MQConstants.OIS_ROUTINGKEY_NAME);
}
/**
* 定义交换机(持久化)
*
* name:交换机的名称
* durable:设置是否持久化。持久化可以将交换机存盘,在服务器重启的时候不会丢失相关信息
* autoDelete:在所在消费者都解除订阅的情况下自动删除
*/
@Bean
public DirectExchange defaultExchange() {
return new DirectExchange(MQConstants.PT_EXCHANGE_NAME, true, false);
}
/**
* 定义一个队列(持久化)
*
* name:队列的名称
* durable:设置是否持久化。持久化的队列会存盘,在RabbitMQ服务重启的时候可以保证不丢失相关信息
*
* @return
*/
@Bean
public Queue oisQueue() {
return new Queue(MQConstants.OIS_QUEUE_NAME, true);
}
/**
* 绑定队列,通过指定交换机和路由key把消息发送到指定的队列(一个队列可以绑定多个路由key)
*
* @return
*/
@Bean
public Binding oisQueueBinding() {
return BindingBuilder.bind(oisQueue()).to(defaultExchange()).with(MQConstants.OIS_ROUTINGKEY_NAME);
}
/**
* 死信队列交换机
*/
@Bean
public DirectExchange delayExchange() {
return new DirectExchange(MQConstants.PT_DELAY_EXCHANGE_NAME, true, false);
}
/**
* 声明死信队列
*/
@Bean()
public Queue oisDelayQueueLevel1() {
return new Queue(MQConstants.OIS_DELAY_QUEUE_LEVEL1_NAME, true, false, false, queueParams);
}
@Bean()
public Queue oisDelayQueueLevel2() {
return new Queue(MQConstants.OIS_DELAY_QUEUE_LEVEL2_NAME, true, false, false, queueParams);
}
@Bean()
public Queue oisDelayQueueLevel3() {
return new Queue(MQConstants.OIS_DELAY_QUEUE_LEVEL3_NAME, true, false, false, queueParams);
}
/**
* 绑定死信队列(我这里用的交换机和普通队列区别开了)
*/
@Bean
public Binding oisDelayQueueLevel1binding() {
return BindingBuilder.bind(oisDelayQueueLevel1()).to(delayExchange()).with(MQConstants.OIS_DELAY_ROUTINGKEY_LEVEL1_NAME);
}
@Bean
public Binding oisDelayQueueLevel2binding() {
return BindingBuilder.bind(oisDelayQueueLevel2()).to(delayExchange()).with(MQConstants.OIS_DELAY_ROUTINGKEY_LEVEL2_NAME);
}
@Bean
public Binding oisDelayQueueLevel3binding() {
return BindingBuilder.bind(oisDelayQueueLevel3()).to(delayExchange()).with(MQConstants.OIS_DELAY_ROUTINGKEY_LEVEL3_NAME);
}
}
上述代码绑定的结果如表格:
队列类型 | 交换机 | 路由 | 队列 |
---|---|---|---|
普通队列 | PT_EXCHANGE | OIS_ROUTINGKEY | OIS_QUEUE |
死信队列 | PT_DELAY_EXCHANGE | OIS_DELAY_ROUTINGKEY_LEVEL1 | OIS_DELAY_QUEUE_LEVEL1 |
死信队列 | PT_DELAY_EXCHANGE | OIS_DELAY_ROUTINGKEY_LEVEL2 | OIS_DELAY_QUEUE_LEVEL2 |
死信队列 | PT_DELAY_EXCHANGE | OIS_DELAY_ROUTINGKEY_LEVEL3 | OIS_DELAY_QUEUE_LEVEL3 |
下面这个作一个简单的解释,声明的那三个死信队列的消息过期后,会通过指定的交换机和路由发送出去,最终是到达队列OIS_QUEUE,我这里设置他们在队列中最大过期时间为8小时(根据自己需要设置),但是消息的过期时间是可以由生产者设置的,但是最好不要超过队列消息的过期时间,否则可能会出现消息丢失。
public static Map<String, Object> queueParams = new HashMap<>();
/**
* 消息过期后,都发送到OIS_QUEUE
*/
static {
//消息在队列中存活的时间
queueParams.put("x-message-ttl", 8 * 60 * 60 * 1000);
//消息过期后要发送的交换机
queueParams.put("x-dead-letter-exchange", MQConstants.PT_EXCHANGE_NAME);
//消息过期后要发送的路由
queueParams.put("x-dead-letter-routing-key", MQConstants.OIS_ROUTINGKEY_NAME);
}
业务逻辑的说明:
QueryPayResultService.java
package com.alian.dlq.service;
import com.alian.common.constant.MQConstants;
import com.alian.common.dto.QueryDto;
import com.alian.dlq.config.AppProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
/**
* 注意这里监听的是OIS_QUEUE,不是监听死信队列
*/
@Slf4j
@Service
@RabbitListener(queues = MQConstants.OIS_QUEUE_NAME, containerFactory = "simpleContainerFactory")
public class QueryPayResultService {
@Autowired
private AppProperties appProperties;
@Autowired
private RabbitTemplate rabbitTemplate;
@RabbitHandler
public void processEmployee(QueryDto queryDto) throws Exception {
log.info("----------开始处理queryDto----------");
log.info("接收到的queryDto信息: {}", queryDto);
//获取当前消息已查询次数(生产者首次发过来的时候是0)
int queryCount = queryDto.getQueryCount();
//获取配置的最大的查询次数
int maxQueryCount = appProperties.getMaxQueryCount();
//先判断查询次数是否超过最大值
if (queryCount >= maxQueryCount) {
log.info("已达到最大查询次数,不再查询");
return;
}
//执行查询的任务,我这里就模拟得了
boolean b = queryPayResult();
//查询次数加1
queryCount = queryCount + 1;
log.info("第【{}】次查询结果返回:{}, ", queryCount, b);
if (b) {
log.info("查询成功,不再查询");
//做业务处理
//...
return;
}
if (queryCount == 3) {
log.info("达到最大查询次数,不再查询");
//做业务处理
//...
return;
}
queryDto.setQueryCount(queryCount);
//做业务处理
//...
//发送消息到下一个死信队列
sendMsgToDelayQueue(queryDto);
log.info("----------queryDto处理完成----------");
//如果是手动应答模式:AcknowledgeMode.MANUAL 则需要调用
}
private void sendMsgToDelayQueue(QueryDto queryDto) {
int queryCount = queryDto.getQueryCount();
MessagePostProcessor processor = message -> {
List<Integer> queryGrap = appProperties.getQueryGrap();
Integer queryGrapTime = queryGrap.get(queryCount);
log.info("第【{}】次查询结果返失败, {}秒后再查询:", queryCount, queryGrapTime);
//消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//消息过期时间,单位毫秒
message.getMessageProperties().setExpiration("" + queryGrapTime * 1000);
return message;
};
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(MQConstants.PT_DELAY_EXCHANGE_NAME, getOisDelayRoutingKey(queryCount + 1), queryDto, processor, correlationData);
}
/**
* 获取当前查询次数后,应该使用的路由key
*
* @param queryCount
* @return
*/
private String getOisDelayRoutingKey(int queryCount) {
return "OIS_DELAY_ROUTINGKEY_LEVEL" + queryCount;
}
/**
* 模拟查询结果
*
* @return
*/
private boolean queryPayResult() {
//我这里模拟查询,生成1-100内的随机数,如果小于20则任务查询成功。
int roundNum = (int) Math.round(Math.random() * (100 - 1) + 1);
return roundNum < 20;
}
}
application.yml
#项目名和端口
server:
port: 8080
servlet:
context-path: /rabbitmq-dlq
#RabbitMQ配置
spring:
rabbitmq:
#地址
addresses: 192.168.0.194
#端口
port: 5672
#用户名
username: test
#密码
password: test
#连接到代理时用的虚拟主机
virtual-host: /
#消费者相关配置
listener:
type: simple
app:
#最大查询次数
max-query-count: 3
#查询间隔
query-grap:
- 5
- 10
- 15
之前我说过,测试生产者和消费者时,最好使用两个系统测试,不然很多问题你会觉得很奇怪,比如序列化问题。
现在我在我之前的文章里加个测试类。链接: RabbitMQ笔记(二)SpringBoot整合RabbitMQ之simple容器(生产者)
TestDelayQueueService.java
package com.alian.publish.service;
import com.alian.common.constant.MQConstants;
import com.alian.common.dto.QueryDto;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.UUID;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestDelayQueueService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMsgToDelayQueue() {
QueryDto queryDto = new QueryDto();
//交易流水
queryDto.setTranSeq("20210901" + System.currentTimeMillis());
//查询次数
queryDto.setQueryCount(0);
MessagePostProcessor processor = message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//过期时间
message.getMessageProperties().setExpiration("" + 5 * 1000);
return message;
};
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
//发送消息到第一个死信队列,注意路由不要写错了
rabbitTemplate.convertAndSend(MQConstants.PT_DELAY_EXCHANGE_NAME, MQConstants.OIS_DELAY_ROUTINGKEY_LEVEL1_NAME, queryDto, processor, correlationData);
try {
//防止生产者发送消息后,关闭了服务,消息回调异常(通道关闭)
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
最后我得再次提醒下,很多小伙伴可能按照我这个执行的时候会出现如下的错误:
消息发送失败,原因为:clean channel shutdown; protocol method: #method<channel.close>(reply-code=200, reply-text=OK, class-id=0, method-id=0)
这是因为ConfirmCallback是异步的,我们使用junit测试发送完消息后就关闭了,也就断开了连接,所以测试时候可以加入一个休眠代码,如上例,或者采用@PostConstruct进行测试。
@PostConstruct
public void sendMsgToDelayQueue() {
}
发送消息到死信队列完成查询:
2021-09-01 14:32:32 064 INFO :----------开始处理queryDto----------
2021-09-01 14:32:32 064 INFO :接收到的queryDto信息: QueryDto{tranSeq='202109011630477946906', queryCount=0}
2021-09-01 14:32:32 065 INFO :第【1】次查询结果返回:false,
2021-09-01 14:32:32 078 INFO :间隔:[5, 10, 15]
2021-09-01 14:32:32 078 INFO :第【1】次查询结果返失败, 10秒后再查询:
2021-09-01 14:32:32 084 INFO :----------queryDto处理完成----------
2021-09-01 14:32:42 094 INFO :----------开始处理queryDto----------
2021-09-01 14:32:42 095 INFO :接收到的queryDto信息: QueryDto{tranSeq='202109011630477946906', queryCount=1}
2021-09-01 14:32:42 095 INFO :第【2】次查询结果返回:false,
2021-09-01 14:32:42 095 INFO :间隔:[5, 10, 15]
2021-09-01 14:32:42 095 INFO :第【2】次查询结果返失败, 15秒后再查询:
2021-09-01 14:32:42 095 INFO :----------queryDto处理完成----------
2021-09-01 14:32:57 102 INFO :----------开始处理queryDto----------
2021-09-01 14:32:57 102 INFO :接收到的queryDto信息: QueryDto{tranSeq='202109011630477946906', queryCount=2}
2021-09-01 14:32:57 102 INFO :第【3】次查询结果返回:true,
2021-09-01 14:32:57 102 INFO :查询成功,不再查询
从结果上我们可以看到,我们总共完成3次查询才查询到结果(随机的),并且是按照我们设定的间隔时间段进行查询的,也没有超过我们设定的最大查询次数,实际中你可以设置多个队列,然后设置不同的时间梯度,完成你的功能,比如:需要异步消息处理的梯度为:15s/15s/30s/3m/10m/20m/30m/30m/60m,你设置9个队列,对应上时间间隔,然后结合我的实例就行了。
重要知识点提醒:
关于本次RabbitMQ中的死信队列就介绍到这里,用到本例的场景可以用到很多的查询,比如延迟查询或者消息推送,比如支付查询,短信推送,邮件发送,也可以用于消息及时通知(结果异步实时梯度通知),不过对于及时消息通知,就不是把消息直接发送到死信队列,而是直接发送到工作队列,也就是消费者监听的队列,只有通知失败,再发送到死信队列,如果有什么疑问也可以评论交流。