大伙可以到我的RabbitMQ专栏获取更多信息
demo示例这里拿
概述
上一篇文章RabbitMQ高级特性——消息的可靠投递(confirm模式与return模式)中介绍了RabbitMQ提供的两种保证消息投递可靠性的方式:
- confirm确认模式
- return 退回模式
这两种方式实质上是提供了对消息生产者侧的消息可靠性保证,保证了生产者的消息能正确的进入预期的队列中。本文将介绍一下Consumer Ack,这是一种面向消费者的消息可靠性保证方案。
Consumer Ack
ack——acknowledge(vt. 承认;答谢;报偿;告知已收到),在RabbitMQ中指代的是消费者收到消息后确认的一种行为,关注点在于消费者能否实际接收到MQ发送的消息。
其提供了三种确认方式:
- 自动确认acknowledge="none":当消费者接收到消息的时候,就会自动给到RabbitMQ一个回执,告诉MQ我已经收到消息了,不在乎消费者接收到消息之后业务处理的成功与否。
- 手动确认acknowledge="manual":当消费者收到消息后,不会立刻告诉RabbitMQ已经收到消息了,而是等待业务处理成功后,通过调用代码的方式手动向MQ确认消息已经收到。当业务处理失败,就可以做一些重试机制,甚至让MQ重新向消费者发送消息都是可以的。
- 根据异常情况确认acknowledge="auto":该方式是通过抛出异常的类型,来做响应的处理(如重发、确认等布拉不拉布拉)。这种方式比较麻烦。
当消息一旦被消费者接收到,会立刻自动向MQ确认接收,并将响应的message从RabbitMQ消息缓存中移除,但是在实际的业务处理中,会出现消息收到了,但是业务处理出现异常的情况,在自动确认的模式下,该条业务处理失败的message就相当于被丢弃了。如果设置了手动确认,则需要在业务处理完成之后,手动调用channel.basicAck(),手动的签收,如果业务处理失败,则手动调用channel.basicNack()方法拒收,并让MQ重新发送该消息。
如果不做任何关于acknowledge的配置,默认就是自动确认签收的。
代码示例
生产者端没有变化,能发消息就可以
/*
* 功能描述: <br>
* 〈测试Consumer Ack〉
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/11/7 21:12
*/
@Test
public void testAck() {
// //消费者接收到该消息,解析到true,就模拟调用channel.basicAck确认签收消息
// rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "boot.test", "test msg send [true]");
//消费者接收到该消息,解析到false,就模拟调用channel.basicNack,拒收消息,让MQ重发
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "boot.test", "test msg send [false]");
}
消费者端打开Ack模式
server:
port: 2002
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: xxxxxxxxx
password: xxxxxxxxx
virtual-host: /LeoLee
listener:
simple:
acknowledge-mode: manual #消费者端确认模式:none自动确认 manual手动确认 auto通过抛出异常的类型,来做响应的处理
concurrency: 1 #当前监听的数量
max-concurrency: 5 #最大监听数量
retry:
enabled: true #是否支持重试
max-attempts: 4 #最大重试次数,默认为3
消费者端创建一个listener并实现ChannelAwareMessageListener接口(其实也可以不实现该接口,只要@RabbitListener标记的方法,或者@RabbitListener标记的类+@RabbitHandler标记的方法的参数列表有[com.rabbitmq.client.Channel]和[org.springframework.amqp.core.Message]两个参数,都可以)
package com.leolee.rabbitmq.MsgListener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @ClassName AckListener
* @Description: Consumer Ack
* 1.设置手动确认签收:acknowledge-mode: manual, retry.enabled: true #是否支持重试
* 2.实现ChannelAwareMessageListener接口,ChannelAwareMessageListener是MessageListener的子接口
* 3.如果消息接收并处理完成,调用channel.basicAck()向MQ确认签收
* 4.如果消息接收但是业务处理失败,调用channel.basicNack()拒收,要求MQ重新发送
* @Author LeoLee
* @Date 2020/11/7
* @Version V1.0
**/
@Component
public class AckListener implements ChannelAwareMessageListener {
@RabbitListener(queues = "boot_queue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
boolean tag = new String(message.getBody()).contains("true");
System.out.println("接收到msg:" + new String(message.getBody()));
//获取mes deliveryTag
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
if (tag) {
System.out.println("业务处理成功");
//手动签收
/*
* deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
*/
channel.basicAck(deliveryTag, false);
} else {
//模拟业务处理失败抛出异常
System.out.println("业务处理失败");
throw new IOException("业务处理失败");
}
} catch (IOException e) {
e.printStackTrace();
/*
* deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
* requeue: true该条消息重新返回MQ queue,MQ broker将会重新发送该条消息
*/
channel.basicNack(deliveryTag, false, true);
//也可以使用channel.basicReject(deliveryTag, requeue),它只能拒收单条消息
//channel.basicReject(deliveryTag, true);
}
}
}
用生产者测试类发送不同的消息给MQ
成功接收并手动确认后,MQ队列就删除了该消息的缓存:
被拒绝的消息会一直发送: