一、消息应答机制
消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉,由于RabbitMQ
一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息,以及后续发送给该消费者的消息也无法接收到。
为了保证消息在发送过程中不丢失,rabbitmq
引入消息应答机制。
消费者在接收到消息并且处理该消息之后,告诉 rabbitmq
它已经处理了,rabbitmq
可以把该消息删除掉了。
二、自动应答
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者出现连接或者 channel
关闭,那么消息就丢失了,当然另一方面这种模式消费者可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。
三、手动消息应答的方法
//用于肯定确认,RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
channel.basicAck();
//用于否定确认
channel.basicNack();
//用于否定确认,不处理该消息了直接拒绝,可以将其丢弃了
channel.basicReject();
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK
确认,RabbitMQ
将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。
四、实现消息手动应答
生产者
public class Producer {
public static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
channel.basicPublish("",QUEUE_NAME,null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:" + message);
}
}
}
工作进程Worker1
沉睡1秒钟后接收消息
public class Worker1 {
public static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("Worker1等待接收消息......");
//声明接收消息回调函数 和 取消消息消费时的回调函数
DeliverCallback deliverCallback = (consumerTag, message) -> {
//沉睡1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String msg = new String(message.getBody(),"UTF-8");
System.out.println("接收到的消息:" + msg);
//手动应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(consumerTag + "消费者取消消费");
};
//false表示不自动应答,采用手动应答
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
工作进程Worker2
沉睡30秒钟后接收消息
public class Worker2 {
public static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("Worker2等待接收消息......");
//声明接收消息回调函数 和 取消消息消费时的回调函数
DeliverCallback deliverCallback = (consumerTag, message) -> {
//沉睡1s
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String msg = new String(message.getBody(),"UTF-8");
System.out.println("接收到的消息:" + msg);
//手动应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(consumerTag + "消费者取消消费");
};
//false表示不自动应答,采用手动应答
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
生产者发出消息
Worker1
接收到消息
如果Worker2
挂掉
那么消息会被自动重新放入到队列中,由Worker1
进行接收