一、概念
消费者完成一个任务可能需要一段时间,若一个消费者处理一个长的任务,处理到一半它突然挂掉了,由于RabbitMq一旦想=向消费者传递了一条消息,便立即将该消息标记为删除。因此这种情况下消费者突然挂掉了,将会丢失正在处理的消息。以及后续发送给该消费者的消息,因为它无法接收到。
为了保证消息在发送过程中不丢失,rabbitmq引入了消息应答机制,消费者在接收到消息并且处理该消息之后。告诉rabbitmq它已经处理了,rabbitmq可以把该消息删除了。
二、自定应答
消息发送后立即被认为已经传递成功,这种模式需要高吞吐量和数据传输安全性方面做权衡,因为在该模式下,在消息接收之前,消费者一侧出现链接或者channl关闭,那么消息就会丢失。
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以 某种速率能够处理这些消息的情况下使用。
三、消息应答的方法(手动应答方法)
1、Channel.basicAck(用于肯定确认),RabbitMq已经知道该消息并且成功被处理,可以将其丢弃了。
2、Channel.basicNack(用于否定确认,即不确定的意思)
3、Channel.basicReject(用于否定确认),与Channel.basicNack相比少一个参数,不处理该消息了直接拒接,可以将其丢弃。
Multiple解释:
手动应答的好处是可以批量应答并且减少网略拥堵
multiple的true和false代表不同意见:
true代表批量应答channel上未应答的消息。比如channel上有传送的tag消息5, 6,7, 8。当前tag是8,那么此时5-8的这些还未应答的消息都会被确认收到消息应答。
false代表只会应答tag=8的消息,5, 6,7这三个消息依然不会被确认收到消息应答。
四、消息自动入队
若消费者由于某些原因失去连接,导致消息未发送ACK确认,RabbitMq会了解到消息未完全处理,并将其重新排队。若此时其它消费者可以处理,它将会很快将消息发送给另一个消费者。此操作可以避免某个消费者偶尔挂掉,不丢失消息。
五、消息应答代码
1、生产者代码
package com.example.rabbitmq.three;
import com.example.rabbitmq.rabbitMqUtils.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
// 消息在手动应答时不丢失,放回到队列中重新消费
public class Task2 {
// 队列名称
public static final String TASK_QUEUE_NAME = "ack_queue";
// 发消息
public static void main(String[] args) throws IOException, TimeoutException {
// 获取信道
Channel channel = RabbitUtils.getChannel();
/*
* 生成一个队列
* 1、队列名称
* 2、队列里面的消息是否持久化(磁盘)默认情况消息存储在内存中
* 3、该队列是否共一个消费者进行消费,是否进行消息共享,true可以多个消费者消费,false只能一个消费者消费
* 4、是否自动删除,最后一个消费者断开链接后,该队列是否自动删除,true自动删除,false不自动删除
* 5、其它参数
* */
channel.queueDeclare(TASK_QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
/*
* 发送一个消息
* 1、发送到哪个交换机
* 2、路由key是哪个,本次是队列的名称
* 3、其他参数信息
* 4、发送消息的消息体
* */
channel.basicPublish("", TASK_QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完成......");
}
}
}
2、消费者代码
package com.example.rabbitmq.three;
import com.example.rabbitmq.rabbitMqUtils.RabbitUtils;
import com.example.rabbitmq.rabbitMqUtils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work03 {
// 队列名称
public static final String TASK_QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
System.out.println("C1等待接收消息处理时间较短");
DeliverCallback deliverCallback = (consumerTag, message) -> {
// 沉睡1秒模拟消费者工作时间较短
SleepUtils.sleep(1);
System.out.println("接收到的消息:" + new String(message.getBody()));
// 手动应答
/**
* 1、消息的标记 tag
* 2、是否批量应答 false:不批量
* */
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
// 取消消费的回调
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(consumerTag + "消息者取消消费回调逻辑");
};
// 采用手动应答
boolean autoAck = false;
// 采用手动应答
channel.basicConsume(TASK_QUEUE_NAME, autoAck, deliverCallback, cancelCallback);
}
}
package com.example.rabbitmq.three;
import com.example.rabbitmq.rabbitMqUtils.RabbitUtils;
import com.example.rabbitmq.rabbitMqUtils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work04 {
// 队列名称
public static final String TASK_QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
System.out.println("C2等待接收消息处理时间较长");
DeliverCallback deliverCallback = (consumerTag, message) -> {
// 沉睡30秒模拟消费者工作时间较长
SleepUtils.sleep(30);
System.out.println("接收到的消息:" + new String(message.getBody()));
// 手动应答
/**
* 1、消息的标记 tag
* 2、是否批量应答 false:不批量
* */
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
// 取消消费的回调
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(consumerTag + "消息者取消消费回调逻辑");
};
// 采用手动应答
boolean autoAck = false;
// 采用手动应答
channel.basicConsume(TASK_QUEUE_NAME, autoAck, deliverCallback, cancelCallback);
}
}
启动生产者代码在控制台输入:aa,bb后将工作时间长的消费者关掉,此时bb消息会重新入队,被另一个正常的消费者消费,保证业务正常运行。