RabbitMQ 问题记录
- RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ。
1.消费者如果正在处理消息的时候突然宕机怎么办?
答:自动确认改变为手动确认ACK
2.当生产者持续发送消息,而消费者处理不过来时该怎么办?
答:使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
3.如果rabbitMq挂了,消息怎么办?
答:持久化:队列,交换机。对消息进行MessageProperties.Persistent(持久化)
4.生产者发消息,但是没有发成功?
答:rabbitMq发消息告诉生产者已经接受到消息(消息重试)
以上四点用来解决消息丢失的问题。
消费者的消息确认机制(Acknowlage)
消息一旦被消费者接收,队列中的消息就会被删除。
那么问题来了:RabbitMQ怎么知道消息被接收了呢?
这就要通过消息确认机制(Acknowlege)来实现了。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:
- 自动ACK:消息一旦被接收,消费者自动发送ACK
- 手动ACK:消息接收后,不会发送ACK,需要手动调用
如何设置,则需要看消息的重要性:
- 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
- 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。
如果要手动ACK,需要改动我们的代码:
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
// 手动进行ACK
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 监听队列,第二个参数false,手动进行ACK
channel.basicConsume(QUEUE_NAME, false, consumer);
消息堆积的处理
启动多个消费者,让多个消费者绑定到同一个队列,共同消费该队列中的消息。
然而现实中,可能不同消费者处理消息的耗时时间不同。所以可以修改设置,让消费者同一时间只接收一条消息,这样处理完成之前,就不会接收更多消息,就可以让处理快的人,接收更多消息 :
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 设置每个消费者同时只能处理一条消息
channel.basicQos(1);
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
持久化 队列,交换机,消息
如何避免消息丢失?
1) 消费者的ACK机制。可以防止消费者丢失消息。
2) 但是,如果在消费者消费之前,MQ就宕机了,消息就没了。
要将消息持久化,前提是:队列、Exchange都持久化
一、交换机持久化
二、队列持久化
三、消息持久化
PERSISTENT_TEXT_PLAIN对应的deliveryMode 2
生产者消息丢失
// 获取通道
Channel channel = connection.createChannel();
//生产者消息确认
channel.confirmSelect();
....
//等待消息确认(失败抛出异常)
channel.waitForConfirmsOrDie();
channel.close();
但是该情况会出现消息重复发送(因网络或者其他问题导致),所以需要对业务进行幂等性(同一接口被重复执行,其结果一致)。