概念
根据上一篇文章的思考中得知,如果任务在执行过程中发生故障导致任务中断,并且这条任务也被队列清除掉了,也就是意味着我们就丢失了一条未被执行完的任务。
消息应答的出现可以保证消息在发送过程中不丢失。其工作原理就是,消费者在接收到了队列分配的任务并且处理完这条任务后,告诉RabbitMQ它已经处理完这条任务,可以从队列中清除掉这条任务了。
自动应答
Rabbit MQ默认的应答机制就是自动应答。消息发送后立即被认为已经传送成功。即当消费者接收到消息后立即通知RabbitMQ,Rabbit MQ此时就可以直接从队列中移除该消息。
缺点:很可能会造成消息没有被完成处理就丢失消息,如:
-
接收过多,来不及被处理的消息,导致大量消息积压而耗尽内存,最终被操作系统强行终止(线程/进程终止任务)。
-
消息处理过程出现问题(Bug),导致程序抛出异常造成消息未处理完毕,而这条消息因为已经被自动应答默认从队列中移除了。
手动应答
手动应答的出现可以很好的处理自动应答的缺点,它可以通过手动控制应答机制,将当前工作线程处理失败的消息通知Rabbit MQ,转交给其他工作线程处理,减低消息丢失的机率。手动应答还可以进行批量应答,减少了网络拥堵的情况,但可能会造成消息不会被完全处理。
生产者:
/**
*
* @author xgs87762
*/
public class RabbitMQAskProducer {
private static final String TAG = "RabbitMQAsk";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnectionUtil.getConnection();
Channel channel = connection.createChannel();
Map<String, Object> params = new HashMap<>();
params.put("author", "代码の诱惑");
params.put("age", "22");
for (int i = 1; i <= 50; i++) {
/**
* 生成队列
* queue 队列名称
* durable 是否持久化
* exclusive 是否独占队列
* autoDelete 是否自动删除队列(服务器不再使用时自动删除队列)
* arguments 队列参数
*/
channel.queueDeclare(TAG, false, false, false, params);
String message = "发布消息";
channel.basicPublish("", TAG, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送了" + i + "条消息");
}
}
}
消费者
/**
* RabbitMQAskCustomer.java
* TODO
* Created on 2023/2/26 18:22
* 1、测试工作线程消费情况
* 2、测试工作线程出现异常,任务自动分配给其他正常工作线程消费
* @author xgs87762
* @version V1.0
*/
public class RabbitMQAskCustomer implements Runnable {
// 队列名称
private static final String TAG = "RabbitMQAsk";
// 计算已执行成功任务数
private static Integer counter = 0;
public static void main(String[] args) {
Runnable askCustomer = new RabbitMQAskCustomer();
for (int i = 1; i <= 10; i++) {
new Thread(askCustomer, "线程" + i).start();
}
}
@Override
public void run() {
try {
String threadName = Thread.currentThread().getName();
Connection connection = RabbitConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
// 消息接收监听
System.out.println(threadName + " ==> 准备开展工作...");
RabbitMQAskCustomer.thread();
// 测试:测试线程执行失败,观察工作任务情况
// 结论:线程8出现问题后,队列自动将工作任务分配给了其他线程消费。
if ("线程8".equals(threadName)) {
throw new IOException("Thread " + threadName + "异常");
}
System.out.println(threadName + " ==> 工作执行完毕!");
/**
* 消息应答
* deliverTag 消息的标记 tag,表明消息的唯一标识
* multiple 是否批量应答(一般不允许应答,防止消息丢失)
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
System.out.println(threadName + " ==> 消息应答提交完毕!");
counter++;
System.out.println("已执行 》" + counter + "《 条任务!");
};
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(threadName + " ==> 消息取消");
};
channel.basicConsume(TAG, false, deliverCallback, cancelCallback);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
public static void thread() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}