目录
4.1.1首先我们来测试一下acknowledge-mode: none不做处理的场景:
4.1.2 测试acknowledge-mode: auto自动处理情况下
1、RabbitMQ消息丢失的可能性
如下图是消息从生产者到消费者的关系图。通过图片我们可以分析,消息从生产者,MQ,消费者这三个环节任一个都有可能丢失。那么下面我们就从这三点进行分析。
1.1 生产者消息丢失场景
-
生产者发送消息时连接MQ失败
-
生产发生消息到达MQ后未找到Exchange
-
生产者发送消息到达MQ的 Exchange 后,未找到合适的 Queue
-
消息到达MQ后,处理消息的进程发生异常
1.2 MQ导致消息丢失
-
消息到达MQ,保存到队列后,尚未消费就突然宕机
1.3 消费者丢失
-
消息接收后尚未处理突然宕机
-
消息接收后处理过程中抛出异常
综上,我们要解决消息丢失问题,保证MQ的可靠性,就必须从3个方面入手:
-
确保生产者一定把消息发送到MQ
-
确保MQ不会将消息弄丢
-
确保消费者一定要处理消息
2、如何保证生产者消息的可靠性
2.1 生产者重试机制
生产者发送消息时,出现了网络故障,导致与MQ的连接中断。为了解决这个问题,SpringAMQP提供的消息发送时的重试机制。当 RabbitTemplate与MQ连接超时后,多次重试。
我们可以在生产者对应的yml配置中配置:
spring:
rabbitmq:
connection-timeout: 1s # 设置MQ的连接超时时间
template:
retry:
enabled: true # 开启超时重试机制
initial-interval: 1000ms # 失败后的初始等待时间
multiplier: 2 # 失败后下次的等待时长倍数,下次等待时长 = initial-interval *multiplier
max-attempts: 3 # 最大重试次数
我这边故意把URL地址写错:
spring:
rabbitmq:
host: 601.204.203.40
我们可以发现总共重试了3次。如图所示:
但是SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的。如果对于业务性能有要求,建议禁用重试机制。
2.2 生产者确认机制
其实一般我们生产者与MQ网络连接比较稳定,所以基本上不用考虑第一种场景。但是还有一些到达MQ之后可能会丢失的场景,比如:
-
生产者发送的消息到达MQ没有找到Exchange
-
生产者发送的消息到达MQ找到Exchange,但是没有找到Queue
-
MQ内部处理消息进程异常
基于上面几种情况,RabbitMQ提供了生产者消息确认机制,包括 Publisher Confirm 和 Publisher Return 两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执。具体主要有以下几种情况:
-
当消息发送到MQ上,但是路由失败了,会返回会通过Publisher Return返回返回信息。同时会返回ack确认信息,表示投递成功。
-
当非持久化消息发送到MQ上,并且入队成功,会返回ACK确认信息,表示投递成功。
-
当持久化消息发送到MQ上,入队成功并且持久化到磁盘,会返回ACK确认信息,表示投递成功。
-
其它情况都会返回NACK,告知投递失败
其中 ack 和 nack 属于Publisher Confirm机制, ack 是投递成功; nack 是投递失败。而 return 则属于Publisher Return机制。默认情况,这两种都是关闭的,需要通过配置开启。
2.3 实现生产者确认
2.3.1 配置yml开启生产者确认
我们在生产者对应yml配置中加入
spring:
rabbitmq:
publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
publisher-returns: true # 开启publisher return机制
其中publisher-confirm-type一共有三种模式:
-
none :关闭confirm机制
-
simple :同步阻塞等待MQ的回执
-
correlated :MQ异步回调返回回执
一般我们都是开启correlated模式。
2.3.2 定义ReturnCallback
每个 RabbitTemplate 只能配置一个 ReturnCallback,我们可以定义一个配置类统一配置。下面我们在生产者中定义配置类ReturnsCallbackConfig:
package com.chenwen.producer.config;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Slf4j
@AllArgsConstructor
@Configuration
public class ReturnsCallbackConfig {
private final RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {