一、消费者假死

代码

项目使用的是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 rabbitMQ 一次消费多条消息_spring

分析

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条消息也丢失了。

springboot rabbitMQ 一次消费多条消息_推送_02

分析

跟消费者假死不同,该现象不是每批消息的最后一个出问题,而是在之前的消息消费时出了问题。
由于是批量确认,那么不管之前的消息是否有确认成功,最后一条消息确认后都会把之前的确认掉,于是造成了消息丢失现象。

解决

解决方式

以下两种方式都可以让消息不丢失

方式一:将批量确认改为单条确认,这样出错的消息就会留在队列里

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条消息出错时,进入了死信队列

springboot rabbitMQ 一次消费多条消息_spring_03

上述两种方式比较

单条确认时,虽然可以避免消息丢失,但每条消息都要和broker通信,高并发情况下会降低吞吐量。
而批量确认配合死信队列,既可以增大吞吐量,又可以避免消息丢失。
所以,第二种方式比较好。