springboot 整合 RabbitMQ
背景
多线程读取大文件后需要分时段进行第三方接口的调用【由于外部接口限制API30s才可以调用一次】,所以我们将使用RabbitMQ的延时消费的方式进行外部接口的调用,以保证项目程序的正常运行
RabbitMQ介绍
RabbitMQ介绍
RabbitMQ 快速下载安装
详情看上 ,本篇文章只解决背景问题,整合springboot+RabbitMQ
springboot 整合 RabbitMQ[延时队列]
延时队列
延时队列是存储延时消息的队列,延时消息就是生产者发送了一条消息,但是不希望该消息不要被立即消费,而是设置一个延时时间,等过了这个时间再消费消息
延时队列安装插件
在RabbitMQ3.6
版本以前一般采用死信队列 + TTL过期时间的方式来实现延迟队列。
从3.6以后的版本,RabbitMQ官方提供了延迟对列的插件官方下载地址rabbitmq_delayed_message_exchange-3.8.0.ez
这个插件放到 RabbitMQ 安装目录的 plugins
文件中
RabbitMQ
安装 sbin
文件中用 cmd
执行命令:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
然后登陆web页面,可以查看到交换机tab下新增了x-delayed-message
插件原理
- 原始的 死信队列+ TTL 的模式,消息首先会路由到一个正常的队列,根据设置的 TTL 进入死信队列,与之不同的是通过
x-delayed-message
声明的交换机,它的消息在发布之后不会立即进入队列,先将消息保存至 Mnesia(一个分布式数据库管理系统,适合于电信和其它需要持续运行和具备软实时特性的 Erlang 应用) - 这个插件将会尝试确认消息是否过期,首先要确保消息的延迟范围是 Delay > 0, Delay =< ?ERL_MAX_T(在 Erlang 中可以被设置的范围为 (2^32)-1 毫秒),如果消息过期通过
x-delayed-type
类型标记的交换机投递至目标队列,整个消息的投递过程也就完成了
代码具体实现
pom.xml
<!--springboot 2.3.1.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.properties
# 由于MQ只在本项目中,未跨项目 所有配置文件一份,跨项目则需要分开配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#开启消息的return机制
spring.rabbitmq.publisher-confirms=true
#确认消息已发送到队列
spring.rabbitmq.publisher-returns=true
#开启消息的confirm机制 correlated:开启;NONE:关闭
spring.rabbitmq.publisher-confirms-type=correlated
#在需要使用消息的return机制时候,此参数必须设置为true
spring.rabbitmq.template.mandatory=true
spring.rabbitmq.listener.type=simple
#消费方消息确认:手动确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.default-requeue-rejected=false
静态常量抽取
/**
* MQ相关常量
**/
public class MqConstant {
// 延时交换机
public static final String MQ_WEBSITE_FILM_DELAY_EXCHANGE = "website_film_delay_exchange";
// 延时队列名称
public static final String MQ_WEBSITE_FILM_QUEUE = "website_film_delay_queue";
// 普通交换机
public static final String MQ_WEBSITE_NORMAL_EXCHANGE = "website_normal_exchange";
// 普通队列名称
public static final String MQ_WEBSITE_NORMAL_QUEUE = "website_normal_queue";
// 普通交换机路由键
public static final String MQ_WEBSITE_NORMAL_ROUTING_KEY = "website_normal_routing_key";
}
配置类
RabbitConfig
主要是队列,交换机的配置绑定
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* RabbitMQ的配置 主要是队列,交换机的配置绑定
**/
@Configuration
public class RabbitConfig {
//https://blog.csdn.net/weixin_38192427/article/details/120509586
/**
* durable="true" 持久化 rabbitmq重启的时候不需要创建新的队列
* exclusive 表示该消息队列是否只在当前connection生效,默认是false
* auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
*/
// 延时队列
@Bean
public Queue delayQueue() {
return new Queue(MqConstant.MQ_WEBSITE_FILM_QUEUE, true,false,false);
}
/**
* 交换机说明:
* durable="true" rabbitmq重启的时候不需要创建新的交换机
* auto-delete 表示交换机没有在使用时将被自动删除 默认是false
* direct交换器相对来说比较简单,匹配规则为:如果路由键匹配,消息就被投送到相关的队列
* topic交换器你采用模糊匹配路由键的原则进行转发消息到队列中
* fanout交换器中没有路由键的概念,他会把消息发送到所有绑定在此交换器上面的队列中。
*/
// 延时交换机 设置
public FanoutExchange delayExchange() {
//使用自定义交换器
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
FanoutExchange fanoutExchange = new FanoutExchange(MqConstant.MQ_WEBSITE_FILM_DELAY_EXCHANGE, true, false, args);
fanoutExchange.setDelayed(true);
return fanoutExchange;
}
// 绑定延时队列与延时交换机
@Bean
public Binding delayBind() {
return BindingBuilder.bind(delayQueue()).to(delayExchange());
}
// ------------------------普通队列------------------------
// 普通队列
@Bean
public Queue normalQueue() {
return new Queue(MqConstant.MQ_WEBSITE_NORMAL_QUEUE, true);
}
// 普通交换机
@Bean
public DirectExchange normalExchange() {
return new DirectExchange(MqConstant.MQ_WEBSITE_NORMAL_EXCHANGE, true, false);
}
// 绑定普通消息队列
@Bean
public Binding normalBind() {
return BindingBuilder.bind(normalQueue()).to(normalExchange()).with(MqConstant.MQ_WEBSITE_NORMAL_ROUTING_KEY);
}
// 定义消息转换器
@Bean
public Jackson2JsonMessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
/** ======================== 定制一些处理策略 =============================*/
/**
* 定制化amqp模版
* <p>
* Rabbit MQ的消息确认有两种。
* <p>
* 一种是消息发送确认:这种是用来确认生产者将消息发送给交换机,交换机传递给队列过程中,消息是否成功投递。
* 发送确认分两步:一是确认是否到达交换机,二是确认是否到达队列
* <p>
* 第二种是消费接收确认:这种是确认消费者是否成功消费了队列中的消息。
*/
// 定义消息模板用于发布消息,并且设置其消息转换器
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 消息发送失败返回到队列中, yml需要配置 publisher-returns: true
rabbitTemplate.setMandatory(true);
/**
* 使用该功能需要开启消息确认,yml需要配置 publisher-confirms: true
* 通过实现ConfirmCallBack接口,用于实现消息发送到交换机Exchange后接收ack回调
* correlationData 消息唯一标志
* ack 确认结果
* cause 失败原因
*/
rabbitTemplate.setConfirmCallback(new MsgSendConfirmCallback());
/**
* 使用该功能需要开启消息返回确认,yml需要配置 publisher-returns: true
* 通过实现ReturnCallback接口,如果消息从交换机发送到对应队列失败时触发
* message 消息主体 message
* replyCode 消息主体 message
* replyText 描述
* exchange 消息使用的交换机
* routingKey 消息使用的路由键
*/
rabbitTemplate.setReturnCallback(new MsgSendReturnCallback());
rabbitTemplate.setMessageConverter(jsonMessageConverter());
return rabbitTemplate;
}
// --------------------------使用RabbitAdmin启动服务便创建交换机和队列--------------------------
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
// 只有设置为 true,spring 才会加载 RabbitAdmin 这个类
rabbitAdmin.setAutoStartup(true);
// 创建延时交换机和对列
rabbitAdmin.declareExchange(delayExchange());
rabbitAdmin.declareQueue(delayQueue());
// 创建普通交换机和对列
rabbitAdmin.declareExchange(normalExchange());
rabbitAdmin.declareQueue(normalQueue());
return new RabbitAdmin(connectionFactory);
}
}
RabbitConfirmConfig
RabbitMQ的配置 消息发送到 exchange,queue 的回调函数
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ的配置 消息发送到 exchange,queue 的回调函数
**/
@Configuration
public class RabbitConfirmConfig {
private static Logger logger = LogManager.getLogger(RabbitConfirmConfig.class);
/** ======================== 定制一些处理策略 =============================*/
/**
* 定制化amqp模版
* <p>
* Rabbit MQ的消息确认有两种。
* <p>
* 一种是消息发送确认:这种是用来确认生产者将消息发送给交换机,交换机传递给队列过程中,消息是否成功投递。
* 发送确认分两步:一是确认是否到达交换机,二是确认是否到达队列
* <p>
* 第二种是消费接收确认:这种是确认消费者是否成功消费了队列中的消息。
* Springboot中使用ConfirmCallback和ReturnCallback
* 注意:
* 在需要使用消息的return机制时候,mandatory参数必须设置为true
* 新版本开启消息的confirm配置publisher-confirms已经过时,改为使用publisher-confirm-type参数设置(correlated:开启;NONE:关闭)
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
// 设置开启Mandatory才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
rabbitTemplate.setMandatory(true);
/**
* 使用该功能需要开启消息确认 无论成功与否,需要配置 publisher-confirms: true
* 通过实现ConfirmCallBack接口,用于实现消息发送到交换机Exchange后接收ack回调
* correlationData 消息唯一标志
* ack 确认结果
* cause 失败原因
*/
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
logger.info("ConfirmCallback:" + "相关数据:" + correlationData);
logger.info("ConfirmCallback:" + "确认情况:" + ack);
logger.info("ConfirmCallback:" + "原因:" + cause);
});
/**
* 消息从Exchange路由到Queue失败的回调
* 使用该功能需要开启消息返回确认,需要配置 publisher-returns: true
* 通过实现ReturnCallback接口,如果消息从交换机发送到对应队列失败时触发
* message 消息主体 message
* replyCode 消息主体 message
* replyText 描述
* exchange 消息使用的交换机
* routingKey 消息使用的路由键
*/
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
logger.info("ReturnCallback:" + "消息:" + message);
logger.info("ReturnCallback:" + "回应码:" + replyCode);
logger.info("ReturnCallback:" + "回应信息:" + replyText);
logger.info("ReturnCallback:" + "交换机:" + exchange);
logger.info("ReturnCallback:" + "路由键:" + routingKey);
});
return rabbitTemplate;
}
}
MsgSendReturnCallback
消息从交换机发送到对应队列失败时触发
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Component
public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
private static Logger logger = LogManager.getLogger(MsgSendReturnCallback.class);
/**
* 使用该功能需要开启消息返回确认,yml需要配置 publisher-returns: true
* message 消息主体 message
* replyCode 消息主体 message
* replyText 描述
* exchange 消息使用的交换机
* routingKey 消息使用的路由键
* <p>
* PS:通过实现ReturnCallback接口,如果消息从交换机发送到对应队列失败时触发
* </p>
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
if (exchange.equals(MqConstant.MQ_WEBSITE_FILM_DELAY_EXCHANGE)) {
// 如果配置了发送回调ReturnCallback,rabbitmq_delayed_message_exchange插件会回调该方法,因为发送方确实没有投递到队列上,只是在交换器上暂存,等过期/时间到了才会发往队列。
// 所以如果是延迟队列的交换器,则直接放过,并不是bug
return;
}
String correlationId = message.getMessageProperties().getCorrelationId();
logger.debug("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {} 路由键: {}", correlationId, replyCode, replyText, exchange, routingKey);
}
}
AckListener
Consumer ACK机制
import com.rabbitmq.client.Channel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* Consumer ACK机制:
* 1. 设置手动签收。acknowledge="manual"
* 2. 让监听器类实现ChannelAwareMessageListener接口
* 3. 如果消息成功处理,则调用channel的 basicAck()签收
* 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
*
*/
@Component
public class AckListener implements ChannelAwareMessageListener {
private static Logger logger = LogManager.getLogger(AckListener.class);
Map<Long,Integer> map;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
logger.info("MQ接收转换消息[{}]",new String(message.getBody()));
//2. 处理业务逻辑
logger.info("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
//e.printStackTrace();
logger.error("MQ拒绝签收逻辑...");
/*
4.拒绝签收
第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
/*if(!map.containsKey(deliveryTag)){
map.put(deliveryTag,1);
channel.basicNack(deliveryTag,true,true);
}else{
//判断是否大于3次
if(>3){
channel.basicNack(deliveryTag,false,true);
//记录
}else{
channel.basicNack(deliveryTag,true,true);
}
}*/
channel.basicNack(deliveryTag,false,false);
//channel.basicReject(deliveryTag,true);
}
}
}
MsgProductionService
发送消息到MQ 业务类 消费生产
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 发送消息到MQ
**/
@Service("msgProductionService")
public class MsgProductionService {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送延时信息
public void sendTimeoutMsg(String exchange,String content, String routingKey, int delay) {
// 通过广播模式发布延时消息,会广播至每个绑定此交换机的队列,这里的路由键没有实质性作用
rabbitTemplate.convertAndSend(exchange, routingKey, content, message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 毫秒为单位,指定此消息的延时时长
message.getMessageProperties().setDelay(delay * 1000);
return message;
});
}
// 发送普通消息
public void sendMsg(String exchange,String routingKey, String content) {
// DirectExchange类型的交换机,必须指定对应的路由键
rabbitTemplate.convertAndSend(exchange, routingKey, content);
}
}
MsgComsumerService
MQ消费 消费者【小编这里是在同一个项目中实现的,生产和消费】
import com.rabbitmq.client.Channel;
import com.zy.website.service.FilmService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
/**
* MQ 消费者
**/
@Service("msgComsumerService")
public class MsgComsumerService {
private static Logger logger = LogManager.getLogger(MsgComsumerService.class);
@Resource
FilmService filmService;//自己业务具体实现类注入
// 监听消费延时消息
@RabbitListener(queues = {"website_film_delay_queue"})
@RabbitHandler
public void process(String content, Message message, Channel channel) throws IOException {
try {
// 消息的可定确认,第二个参数如果为true将一次性确认所有小于deliveryTag的消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//调用方法消费消息 自己业务具体实现类
filmService.getFilmInfoByExternalApi(content);
logger.info("延迟队列消息[{}]被消费!!",content);
} catch (Exception e) {
logger.error("延迟队列消息 处理失败:{}", e.getMessage());
// 直接拒绝消费该消息,后面的参数一定要是false,否则会重新进入业务队列,不会进入死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
/*// ack返回false,requeue-true并重新回到队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);*/
}
}
// 消费普通消息
@RabbitListener(queues = {"website_normal_queue"})
@RabbitHandler
public void process1(String content, Message message, Channel channel) throws IOException {
try {
logger.info("普通队列的内容[{}]", content);
// 消息的可定确认,第二个参数如果为true将一次性确认所有小于deliveryTag的消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
logger.info("普通信息处理完毕");
} catch (Exception e) {
logger.error("处理失败:{}", e.getMessage());
// 直接拒绝消费该消息,后面的参数一定要是false,否则会重新进入业务队列,不会进入死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
**代码实现完成,这只是简单实现,复杂还需要研究。。。。。**
RabbitMQ的具体详细配置项
基础配置项
spring.rabbitmq.host: 默认localhost
spring.rabbitmq.port: 默认5672
spring.rabbitmq.username: 用户名
spring.rabbitmq.password: 密码
spring.rabbitmq.virtual-host: 连接到代理时用的虚拟主机
spring.rabbitmq.addresses: 连接到server的地址列表(以逗号分隔),先addresses后host
spring.rabbitmq.requested-heartbeat: 请求心跳超时时间,0为不指定,如果不指定时间单位默认为妙
spring.rabbitmq.publisher-confirms: 是否启用【发布确认】,默认false
spring.rabbitmq.publisher-returns: 是否启用【发布返回】,默认false
spring.rabbitmq.connection-timeout: 连接超时时间,单位毫秒,0表示永不超时
SSL
spring.rabbitmq.ssl.enabled: 是否支持ssl,默认false
spring.rabbitmq.ssl.key-store: 持有SSL certificate的key store的路径
spring.rabbitmq.ssl.key-store-password: 访问key store的密码
spring.rabbitmq.ssl.trust-store: 持有SSL certificates的Trust store
spring.rabbitmq.ssl.trust-store-password: 访问trust store的密码
spring.rabbitmq.ssl.trust-store-type=JKS:Trust store 类型.
spring.rabbitmq.ssl.algorithm: ssl使用的算法,默认由rabiitClient配置
spring.rabbitmq.ssl.validate-server-certificate=true:是否启用服务端证书验证
spring.rabbitmq.ssl.verify-hostname=true 是否启用主机验证
缓存cache
spring.rabbitmq.cache.channel.size: 缓存中保持的channel数量
spring.rabbitmq.cache.channel.checkout-timeout: 当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
spring.rabbitmq.cache.connection.size: 缓存的channel数,只有是CONNECTION模式时生效
spring.rabbitmq.cache.connection.mode=channel: 连接工厂缓存模式:channel 和 connection
Listener
spring.rabbitmq.listener.type=simple: 容器类型.simple或direct
spring.rabbitmq.listener.simple.auto-startup=true: 是否启动时自动启动容器
spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量
spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量
spring.rabbitmq.listener.simple.prefetch: 一个消费者最多可处理的nack消息数量,如果有事务的话,必须大于等于transaction数量.
spring.rabbitmq.listener.simple.transaction-size: 当ack模式为auto时,一个事务(ack间)处理的消息数量,最好是小于等于prefetch的数量.若大于prefetch, 则prefetch将增加到这个值
spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.missing-queues-fatal=true 若容器声明的队列在代理上不可用,是否失败; 或者运行时一个多多个队列被删除,是否停止容器
spring.rabbitmq.listener.simple.idle-event-interval: 发布空闲容器的时间间隔,单位毫秒
spring.rabbitmq.listener.simple.retry.enabled=false: 监听重试是否可用
spring.rabbitmq.listener.simple.retry.max-attempts=3: 最大重试次数
spring.rabbitmq.listener.simple.retry.max-interval=10000ms: 最大重试时间间隔
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms:第一次和第二次尝试传递消息的时间间隔
spring.rabbitmq.listener.simple.retry.multiplier=1: 应用于上一重试间隔的乘数
spring.rabbitmq.listener.simple.retry.stateless=true: 重试时有状态or无状态
spring.rabbitmq.listener.direct.acknowledge-mode= ack模式
spring.rabbitmq.listener.direct.auto-startup=true 是否在启动时自动启动容器
spring.rabbitmq.listener.direct.consumers-per-queue= 每个队列消费者数量.
spring.rabbitmq.listener.direct.default-requeue-rejected= 默认是否将拒绝传送的消息重新入队.
spring.rabbitmq.listener.direct.idle-event-interval= 空闲容器事件发布时间间隔.
spring.rabbitmq.listener.direct.missing-queues-fatal=false若容器声明的队列在代理上不可用,是否失败.
spring.rabbitmq.listener.direct.prefetch= 每个消费者可最大处理的nack消息数量.
spring.rabbitmq.listener.direct.retry.enabled=false 是否启用发布重试机制.
spring.rabbitmq.listener.direct.retry.initial-interval=1000ms # Duration between the first and second attempt to deliver a message.
spring.rabbitmq.listener.direct.retry.max-attempts=3 # Maximum number of attempts to deliver a message.
spring.rabbitmq.listener.direct.retry.max-interval=10000ms # Maximum duration between attempts.
spring.rabbitmq.listener.direct.retry.multiplier=1 # Multiplier to apply to the previous retry interval.
spring.rabbitmq.listener.direct.retry.stateless=true # Whether retries are stateless or stateful.
Template
spring.rabbitmq.template.mandatory: 启用强制信息;默认false
spring.rabbitmq.template.receive-timeout: receive() 操作的超时时间
spring.rabbitmq.template.reply-timeout: sendAndReceive() 操作的超时时间
spring.rabbitmq.template.retry.enabled=false: 发送重试是否可用
spring.rabbitmq.template.retry.max-attempts=3: 最大重试次数
spring.rabbitmq.template.retry.initial-interva=1000msl: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.template.retry.multiplier=1: 应用于上一重试间隔的乘数
spring.rabbitmq.template.retry.max-interval=10000: 最大重试时间间隔