消息可靠性
spring boot Rabbit高级教程_springboot 配置rabbitmq timeout-优快云博客
RabbitMq 消息确认机制详解_rabbitmq消息确认机制-优快云博客
其中的每一步都可能导致消息丢失,常见的丢失原因包括:
- 发送时丢失:
-
- 生产者发送的消息未送达exchange
- 消息到达exchange后未到达queue
- MQ宕机,queue将消息丢失
- consumer接收到消息后未消费就宕机
针对这些问题,RabbitMQ分别给出了解决方案:
- 生产者重试机制
- 生产者确认机制
- mq持久化
- 消费者确认机制
- 失败重试机制
生产者重试机制
就是生产者发送消息时,出现了网络故障,导致与MQ的连接中断。
为了解决这个问题,SpringAMQP提供的消息发送时的重试机制。即:当RabbitTemplate与MQ连接超时后,多次重试。
修改publisher模块的application.yaml文件,添加下面的内容:
spring:
rabbitmq:
connection-timeout: 1s # 设置MQ的连接超时时间
template:
retry:
enabled: true # 开启超时重试机制
initial-interval: 1000ms # 失败后的初始等待时间
multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier
max-attempts: 3 # 最大重试次数
注意:当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的。
如果对于业务性能有要求,建议禁用重试机制。如果一定要使用,请合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码。
生产者确认机制
rabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
返回结果有两种方式:
- publisher-confirm,发送者确认
-
- 消息成功投递到交换机,返回ack
- 消息未投递到交换机,返回nack
- publisher-return,发送者回执,一般在真实的生产环境中是不需要开启的
-
- 消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。
默认两种机制都是关闭状态,需要通过配置文件来开启。
在publisher模块的application.yaml中添加配置:
spring:
rabbitmq:
publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
publisher-returns: true # 开启publisher return机制
template:
mandatory: true
- 这里publisher-confirm-type有三种模式可选:
-
- none:关闭confirm机制
- simple:同步阻塞等待MQ的回执
- correlated:MQ异步回调返回回执,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
一般我们推荐使用correlated,回调机制。
- publish-returns:开启publish-return功能,同样是基于callback机制,不过是定义ReturnCallback
- template.mandatory:定义消息路由失败时的策略。
true,则调用ReturnCallback;
false:则直接丢弃消息
定义Return回调
每个RabbitTemplate只能配置一个ReturnCallback:
在消息投递给队列失败时才触发这个回调
修改publisher服务,添加一个:
@Configuration
public class RabbitConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitConfig.class);
@Autowired
private CachingConnectionFactory connectionFactory;
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 设置消息转换器为json格式
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
// 消息是否成功发送到Exchange
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
LOGGER.info("消息发送到Exchange成功,{}", correlationData);
} else {
LOGGER.error("消息发送到Exchange失败, {}, cause: {}", correlationData, cause);
}
});
// 触发setReturnCallback回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
rabbitTemplate.setMandatory(true);
// 消息是否从Exchange路由到Queue, 注意: 这是一个失败回调, 只有消息从Exchange路由到Queue失败才会回调这个方法
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
LOGGER.error("消息从Exchange路由到Queue失败: exchange: {}, route: {}, replyCode: {}, replyText: {}, message: {}", exchange, routingKey, replyCode, replyText, message);
});
return rabbitTemplate;
}
}
定义ConfirmCallback
ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同。
在publisher服务的cn.itcast.mq.spring.SpringAmqpTest类中,定义一个单元测试方法:
这种方式可以对特点类型的消息进行回调
public void testSendMessage2SimpleQueue() throws InterruptedException {
// 1.消息体
String message = "hello, spring amqp!";
// 2.发送消息到服务器中(附带消息ID)
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 3.添加callback
// correlationData.getFuture().addCallback(
// result -> {
// if(result.isAck()){
// // 3.1.ack,消息成功
// log.debug("消息发送成功, ID:{}", correlationData.getId());
// }else{
// // 3.2.nack,消息失败
// log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
// }
// },
// //Future发生异常时的处理逻辑,基本不会触发
// ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
// );
// 4.发送消息
rabbitTemplate.convertAndSend("task.direct", "task", message, correlationData);
// 休眠一会儿,等待ack回执
Thread.sleep(2000);
}
全局配置