一 确认种类
每一个颜色块之间都存在着消息的确认机制,我们大概分为两大类,发送方确认和接收方确认,其中发送方确认又分为生产者到交换器到确认和交换器到队列的确认。即:
- 消息发送确认。这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。
- 消费接收确认。这种是确认消费者是否成功消费了队列中的消息
二 消息发送确认
2.1 ConfirmCallback
通过实现ConfirmCallBack
接口,消息发送到交换器Exchange后触发回调。
RabbitMQ的消息确认机制
使用该功能需要开启确认,spring-boot中配置如下:
spring.rabbitmq.publisher-confirms = true
2.2 ReturnCallback
通过实现ReturnCallback
接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)
使用该功能需要开启确认,spring-boot中配置如下:
spring.rabbitmq.publisher-returns = true
三 消息接收确认
3.1 消息消费者如何通知 Rabbit 消息消费成功?
- 消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK
- 自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
- 如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
- 如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者
- 如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限
- ACK 机制还可以起到限流作用,比如在接收到某条消息时休眠几秒钟
3.2 确认模式
AcknowledgeMode.NONE:不确认
AcknowledgeMode.AUTO:自动确认
AcknowledgeMode.MANUAL:手动确认
spring-boot中配置方法:
spring.rabbitmq.listener.simple.acknowledge-mode = manual
3.3 手动确认
未确认的消息数
上图为channel中未被消费者确认的消息数。
通过RabbitMQ的host地址加上默认端口号15672访问管理界面。
@Service
public class AsyncConfirmConsumer {
@RabbitListener(queues = "confirm_queue")
@RabbitHandler
public void asyncConfirm(Order order, Message message, Channel channel) throws IOException {
try {
System.out.println("消费消息:" + order.getName());
// int a = 1 / 0;
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
System.out.println("消费消息确认" + message.getMessageProperties().getConsumerQueue() + ",接收到了回调方法");
} catch (Exception e) {
//重新回到队列
// channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
// System.out.println("尝试重发:" + message.getMessageProperties().getConsumerQueue());
//requeue =true 重回队列,false 丢弃
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
// TODO 该消息已经导致异常,重发无意义,自己实现补偿机制
}
}
}
3.3.1 成功确认
void basicAck(long deliveryTag, boolean multiple) throws IOException;
deliveryTag
:该消息的index
multiple
:是否批量. true:将一次性ack所有小于deliveryTag的消息。
消费者成功处理后,调用channel.basicAck(message.getMessageProperties().getDeliveryTag(), false)
方法对消息进行确认。
3.3.2 失败确认
void basicNack(long deliveryTag, boolean multiple, boolean requeue)throws IOException;
deliveryTag
:该消息的index。
multiple
:是否批量. true:将一次性拒绝所有小于deliveryTag的消息。
requeue
:被拒绝的是否重新入队列。
void basicReject(long deliveryTag, boolean requeue) throws IOException;
deliveryTag
:该消息的index。
requeue
:被拒绝的是否重新入队列。
channel.basicNack 与 channel.basicReject 的区别在于basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息。
思考
(1)手动确认模式,消息手动拒绝中如果requeue为true会重新放入队列,但是如果消费者在处理过程中一直抛出异常,会导致入队-》拒绝-》入队的循环,该怎么处理呢?
第一种方法是根据异常类型来选择是否重新放入队列。
第二种方法是先成功确认,然后通过channel.basicPublish()重新发布这个消息。重新发布的消息网上说会放到队列后面,进而不会影响已经进入队列的消息处理。
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)throws IOException;
(2)消息确认的作用是什么?
为了防止消息丢失。消息丢失分为发送丢失和消费者处理丢失,相应的也有两种确认机制。
(3)可以自动创建队列,也可以手动创建队列,如果自动创建队列,那么是谁负责创建队列呢?是生产者?还是消费者?
如果队列不存在,当然消费者不会收到任何的消息。但是如果队列不存在,那么生产者发送的消息就会丢失。所以,为了数据不丢失,消费者和生产者都可以创建队列。那么如果创建一个已经存在的队列呢?那么不会有任何的影响。需要注意的是没有任何的影响,也就是说第二次创建如果参数和第一次不一样,那么该操作虽然成功,但是队列属性并不会改变。
队列对于负载均衡的处理是完美的。对于多个消费者来说,RabbitMQ使用轮询的方式均衡的发送给不同的消费者。