这篇文章主要讲 RabbitMQ 中 消费者 ack 以及 生产者 confirms。
如上图,生产者把消息发送到 RabbitMQ,然后 RabbitMQ 再把消息投递到消费者。
生产者和 RabbitMQ,以及 RabbitMQ 和消费者都是通过 TCP 连接,但是他们之间是通过信道(Channel)传递数据的。多个线程共享一个连接,但是每个线程拥有独自的信道。
消费者 ack
问题:怎么保证 RabbitMQ 投递的消息被成功投递到了消费者?
RabbitMQ 投递的消息,刚投递一半,产生了网络抖动,就有可能到不了消费者。
解决办法:
RabbitMQ 对消费者说:“如果你成功接收到了消息,给我说确认收到了,不然我就当你没有收到,我还会重新投递”
在 RabbitMQ 中,有两种 acknowledgement 模式。
自动 acknowledgement 模式
这也称作发后即忘模式。
在这种模式下,RabbitMQ 投递了消息,在投递成功之前,如果消费者的 TCP 连接 或者 channel 关闭了,这条消息就会丢失。
会有丢失消息问题。
手动 acknowledgement 模式
在这种模式下,RabbitMQ 投递了消息,在投递成功之前,如果消费者的 TCP 连接 或者 channel 关闭了,导致这条消息没有被 acked,RabbitMQ 会自动把当前消息重新入队,再次投递。
会有重复投递消息的问题,所以消费者得准备好处理重复消息的问题,就是所谓的:幂等性。
为了启用 手动 ack 模式,消费者需要实现 ChannelAwareMessageListener
接口。
@Component
public class Consumer implements ChannelAwareMessageListener {
@Autowired
private MessageConverter messageConverter;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
MessageProperties messageProperties = message.getMessageProperties();
// 代表投递的标识符,唯一标识了当前信道上的投递,通过 deliveryTag ,消费者就可以告诉 RabbitMQ 确认收到了当前消息,见下面的方法
long deliveryTag = messageProperties.getDeliveryTag();
// 如果是重复投递的消息,redelivered 为 true
Boolean redelivered = messageProperties.getRedelivered();
// 获取生产者发送的原始消息
Object originalMessage = messageConverter.fromMessage(message);
Console.log("consume message = {} , deliveryTag = {} , redelivered = {}"
, originalMessage, deliveryTag, redelivered);
// 代表消费者确认收到当前消息,第二个参数表示一次是否 ack 多条消息
channel.basicAck(deliveryTag, false);
// 代表消费者拒绝一条或者多条消息,第二个参数表示一次是否拒绝多条消息,第三个参数表示是否把当前消息重新入队
// channel.basicNack(deliveryTag, false, false);