RabbitMQ 消费者确认auto 和 manual 模式对异常的处理区别(含重试、requeue的影响)

本文用于解答下述疑问:

消息在下面四个条件的处理方式:

  • 两种模式
  • 是否有异常、是否捕获异常
  • 是否设置重试
  • requeuetrue / false ( + default-requeue-rejected的影响)

看似是 2 4 种方案,让人头疼,但其实没那么复杂。总结中也会给出简洁的答案。

文章目录

    • 正文
      • auto自动确认
      • manual人工确认
      • default-requeue-rejected 属性
      • 导致死循环的情况
    • 总结

正文

本文环境: springboot 2.1.9.RELEASE + amqp-client-5.4.3.jar

本文内容都是 direct相关的配置,这里的direct不是指直连模式的那个,而是指springbbot配置中rabbitMQ的属性。
application.properties的配置

#注意用direct要声明
spring.rabbitmq.listener.type=direct
#是direct
spring.rabbitmq.listener.direct.xxxx
#而不是 spring.rabbitmq.listener.simple.xxxx

不了解两者区别的可以参考一下RabbitMQ传输原理、五种模式

本文所说的模式仅限于automanual两种,none不做讨论(毕竟就是"不管")

本文也不讨论这两种模式的优缺点,(auto时,源源不断地发送消息之类)


为简化讨论,"异常"只分两种: (这也是JVM处理的逻辑)

  • 没有异常 和 有异常捕获了,都视为没有异常
  • 有异常, 或者 捕获了异常再抛出、手动抛出,都视为有异常
    (补充:全局异常处理器无法生效,只看该方法内有没有抛出)

首先前置知识:

  • RabbitMQ对异常是一无所知的,它只根据收到的ack / nack / reject 以及 requeue 来处理该消息

  • 异常是在消费端内部处理的

  • 无论哪种模式无论哪种异常,只要设置了重试,抛出异常,消费端就会进行重试。

    超出重试限制后,会根据当前的模式进行不同处理


auto自动确认

  • 消息成功被消费,没有抛出异常,则自动确认,回复ack。

    不涉及requeue,毕竟已经成功了。requeue是对被拒绝的消息生效。

  • 当抛出 ImmediateAcknowledgeAmqpException 异常,则视为成功消费,确认该消息。

    笔者看法:这个异常应该是设计来手动抛出,让MQ对该消息进行确认。

  • 当抛出 AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝,且 requeue = false(该异常会在重试超过限制后抛出)

  • 其他的异常,则消息会被拒绝,且 requeue = true


manual人工确认

无论有没有异常,标准只有是否主动调用basicAck()、basicNack()等方法没有调用则一直阻塞

  • 因此有异常时,(有重试就重试),抛出异常后,没有调用时,还是会一直阻塞。

    即使是auto模式的那两个特殊的异常,在manual中都是一样的,不会有特殊处理


default-requeue-rejected 属性

default-requeue-rejected属性的准确定义是:

【 (若有重试)重试次数超过限制后】,是否将被拒绝的消息重新入队。

注意:对象是被拒绝的消息。(这就是requeue的作用对象)

所以不要期望能通过该属性解决manual模式的异常的问题,如果你根本没响应消息,就不符合"被拒绝"的概念。

这里需要注意优先级的问题:

最高优先级:

  • AmqpRejectAndDontRequeueException处理的requeue = false (剩下那个特殊异常视为成功,与此处无关)
  • basicNack() 的 boolean requeue 参数

次优先级:default-requeue-rejected


最低优先级:auto模式中,处理其他异常时,拒绝消息,且 requeue = true

这里有两个点:

  • AmqpRejectAndDontRequeueException的结果不会被该属性覆盖,所以我们可以确保:消息超出重试限制后,被拒绝,且requeue = false ,成为死信。可以被死信队列所接收
  • basicNack()requeue参数是必须设置的

所以该属性的意义就在于覆盖掉最低优先级中,对其他异常的重入队true,防止死循环

(注:此时也是成为了死信的)


导致死循环的情况

首先需要提及一下:

无论哪种模式。requeue = true ,消息被拒绝,重回队列后,消息还是在队列头部。而不会到队列尾部。

而此处讨论导致死循环的,是指:消息本身有问题,且假设同一个消费者

(多个消费者可能不会死循环,但还是会消耗大量资源)

  • auto模式,没有设置重试,且requeue = true(默认的)

    抛异常(指两个特殊异常外的) -> 消息被拒绝,重入队头 -> 再发送 -> 抛异常 …

    因为如果设置了重试,会触发AmqpRejectAndDontRequeueException 异常,不赘述

  • maual模式,Nack / reject 时,requeue = true
    拒绝 -> 重入队 -> 再发送 -> 拒绝 …

对于死循环,有两种解决方法:

  1. 消费端中,直接重新发送该消息,此时会到队列尾部,然后删除该消息。

    但缺点是,该消息如果本身有问题,也会无限重复,只是不是死循环,一样是浪费资源的。需要通过限制处理次数,记录下来,人工解决。

  2. 设置requeue = false,配合死信队列记录。

    (auto通过default-requeue-rejected覆盖,manual直接在basicNack中指定)


总结

  • 两种模式:

    manual简单暴力,只看有否调用api进行回应

  • 是否有异常、是否捕获异常

    • 对MQ,标准只有收到的 ack / nackrequeue属性
    • 消费端识别异常,有重试则重试,无重试
  • 是否设置重试

    重试只是消费端的一种处理方式,为了减少网络等影响。

    重试最大的意义:

    能够抛出特殊异常:AmqpRejectAndDontRequeueExceptionauto模式下识别该异常,直接拒绝且requeue = false,让消息成为死信。

  • requeuetrue / false ( + default-requeue-rejected的影响)

    requeue只对被拒绝的消息有效

    default-requeue-rejected 唯一意义在于 :

    设置为false,让auto模式下,将非特殊异常而被拒绝的消息,覆盖为不重入队列,成为死信,防止死循环。


参考博文:

Rabbitmq和spring-rabbitmq中的DLX-拒绝消息的一些注意事项


本文完,有误欢迎指出。

转载请注明原文地址,https://blog.csdn.net/Unknownfuture/article/details/107950572

你可能感兴趣的:(RabbitMQ,笔记,java,rabbitmq)