一、消费者假死
代码
项目使用的是springboot整合rabbitmq。其中消费者代码如下,用的是手动批量确认模式
@Slf4j
@Component
public class MessageListener {
@RabbitListener(queues = "jack.queue")
public void process(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel){
log.info(Thread.currentThread().getName() + "=============接收到消息=============" + message + "--" + deliveryTag);
//业务代码start
......
......
//业务代码end
try {
//手动批量确认
channel.basicAck(deliveryTag,true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
问题
消费者处理业务抛出异常后,虽然生产者一直在发送消息,但是此时消费消费不到新消息了,即"消费者假死"
观察控制台,发现10条消息一直处于待确认状态,990条一直处于待消费状态
分析
springboot整合的mq,默认用的是Qos模式,且每次只推送10条消息到消费者。Qos的介绍参考Qos方式。
同时,手动确认采用的是批量确认模式,也就是说,这10条消息要等到最后一条确认后,才一次性确认。
当消费到最后一条消息时,如果业务代码刚好抛出异常,那么就走不到basicAck这个代码,于是这10条消息都无法确认了,broker也就不会推送新消息给消费者。
解决
将批量确认改为单条确认,于是消费正常的消息经过确认后,qos就会继续推送新的消息给消费者了
channel.basicAck(deliveryTag,false);
不过极端情况下,如果这10条消息都抛出了异常导致无法确认,那么还是会出现上述问题。
二、消息丢失
代码
上述代码,变成如下
@Slf4j
@Component
public class MessageListener {
@RabbitListener(queues = "jack.queue")
public void process(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel){
log.info(Thread.currentThread().getName() + "=============接收到消息=============" + message + "--" + deliveryTag);
try {
if ("Jack18".equals(message)) {
int i = 1 / 0;
}
}catch (Exception e) {
}
try {
channel.basicAck(deliveryTag,true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
问题
生产者发送了20条消息,除了第18条,其他消息消费都是正常的。
但是观察控制台,发现消息都消费完了,第18条消息也丢失了。
分析
跟消费者假死不同,该现象不是每批消息的最后一个出问题,而是在之前的消息消费时出了问题。
由于是批量确认,那么不管之前的消息是否有确认成功,最后一条消息确认后都会把之前的确认掉,于是造成了消息丢失现象。
解决
解决方式
以下两种方式都可以让消息不丢失
方式一:将批量确认改为单条确认,这样出错的消息就会留在队列里
channel.basicAck(deliveryTag,false);
方式二:还是批量确认模式。同时设置死信队列,当消息出错时,丢入死信队列
try {
if ("Jack18".equals(message)) {
int i = 1 / 0;
}
} catch (Exception e) {
//丢死信队列
try {
channel.basicNack(deliveryTag, false, false);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
try {
channel.basicAck(deliveryTag,true);
} catch (IOException e) {
e.printStackTrace();
}
观察控制台,发送当第18条消息出错时,进入了死信队列
上述两种方式比较
单条确认时,虽然可以避免消息丢失,但每条消息都要和broker通信,高并发情况下会降低吞吐量。
而批量确认配合死信队列,既可以增大吞吐量,又可以避免消息丢失。
所以,第二种方式比较好。