为什么需要接收确认
RabbitMQ默认会在消息被消费者接收后,立即确认。但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
另外一种情况就是,我们在spring中处理消息时,即使消息处理没出异常,但是后续代码出异常造成回滚,这样其实也相当于丢失消息。
所以一般情况下,手动确认要比较好一些。
消息确认模式
AcknowledgeMode.NONE:自动确认
AcknowledgeMode.AUTO:根据情况确认
AcknowledgeMode.MANUAL:手动确认
开启消息手动确认
配置文件中:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
配置类:
@Bean
public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//使用jackson进行消息序列与反序列
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 开启手动 ack
return factory;
}
确认消息的API
通过com.rabbitmq.client.Channel中的方法来进行消息确认
//确认消费成功
void basicAck(long deliveryTag, boolean multiple) throws IOException;
//确认消费失败
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
throws IOException;
//拒绝消息
void basicReject(long deliveryTag, boolean requeue) throws IOException;
关于参数:
deliveryTag:RabbitMQ 向该 Channel 投递的这条消息的唯一标
识
multiple:是否批量处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
requeue:是否重新放入队列
消息消费过程中异常处理
大致有以下三种处理方式
-
内部catch后直接处理,然后使用channel对消息进行确认。
这种需要设置手动处理,然后用channel进行消息确认。这种操作方式,若是消费出现异常,调用basicNack方法时的参数requeue若是true,消息就会一直被重新放入队列,然后消费,然后异常,死循环。
处理方式:
为了避免消息处理异常造成死循环,我们可以将requeue设置成fasle,这时消息会进入“死信”队列。然后我们可以监听死信队列来做异常处理。
我们可以在设置队列时指定死信队列的交换机和路由key://声明队列,并给队列增加x-dead-letter-exchange和x-dead-letter-routing-key参数,用于指定死信队列的路由和routingKey @Bean public Queue queue(){ Map<String, Object> args = new HashMap<String, Object>(); args.put("x-dead-letter-exchange",IntegralConstant.DEAD_EXCHANGE_NAME); args.put("x-dead-letter-routing-key",IntegralConstant.DEAD_ROUTING_KEY); return new Queue(IntegralConstant.QUEUE_NAME, true, false, false, args); }
然后我们在进行消息确认时就可以做如下操作:
``` /消息确认时使用nack,并且requeue参数传false channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); ```
顺便提一下消息变成死信的情况:
消息被拒绝(basic.reject/ basic.nack)并且requeue=false
消息TTL过期(参考:RabbitMQ之TTL(Time-To-Live 过期时间))
队列达到最大长度 -
配置RepublishMessageRecoverer将处理异常的消息发送到指定队列专门处理或记录
注意,这是在消息方法中不处理异常的情况下,spring.rabbitmq.listener.retry配置的重试次数用完以后还是抛异常的话才会调用这个RepublishMessageRecoverer来处理@Bean public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){ return new RepublishMessageRecoverer(rabbitTemplate, "errorExchange", "errorRoutingKey"); }
-
ErrorHandler
利用@RabbitListener中的属性errorHandler实现一个异常监听处理器:/** * * @return */ @Bean public RabbitListenerErrorHandler rabbitListenerErrorHandler() { return (amqpMessage, message, exception) -> { System.out.println("进入handler"); System.out.println(new String(amqpMessage.getBody())); return null; }; }
总结
- 若是开启的自动确认,可以使用第二种和第三种方法进行消费异常的处理。
- 若是开启的手动确认,监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败,使用第一种方法即可。
- 手动确认情况下,推荐在消息消费失败时,将消息放入死信队列(requeue=false)