跳到主要内容
版本:4.1.1

异常处理

Doubao 中英对照 Exception Handling

使用 RabbitMQ Java 客户端的许多操作都可能抛出受检异常。例如,在很多情况下可能抛出 IOException 实例。RabbitTemplate、SimpleMessageListenerContainer 以及其他 Spring AMQP 组件会捕获这些异常,并将它们转换为 AmqpException 体系中的某个异常。这些异常定义在 'org.springframework.amqp' 包中,AmqpException 是该体系的基类。

当监听器抛出异常时,它会被包装在一个 ListenerExecutionFailedException 中。通常消息会被 broker 拒绝并重新入队。将 defaultRequeueRejected 设置为 false 会导致消息被丢弃(或路由到死信交换机)。正如消息监听器与异步情形中所讨论的,监听器可以抛出 AmqpRejectAndDontRequeueException(或 ImmediateRequeueAmqpException)来有条件地控制此行为。

然而,有一类错误是监听器无法控制其行为的。当遇到无法转换的消息时(例如无效的 content_encoding 头),某些异常会在消息到达用户代码之前抛出。如果 defaultRequeueRejected 设置为 true(默认)(或抛出 ImmediateRequeueAmqpException),此类消息会被反复重新投递。在 1.3.2 版本之前,用户需要编写自定义的 ErrorHandler,如异常处理中所述,以避免这种情况。

从 1.3.2 版本开始,默认的 ErrorHandler 现在是一个 ConditionalRejectingErrorHandler,它会拒绝(并不再入队)因不可恢复错误而失败的消息。具体来说,它会拒绝因以下错误而失败的消息:

  • o.s.amqp…​MessageConversionException:在使用 MessageConverter 转换传入消息负载时可能抛出。
  • o.s.messaging…​MessageConversionException:在映射到 @RabbitListener 方法时如果需要额外转换,可能由转换服务抛出。
  • o.s.messaging…​MethodArgumentNotValidException:如果监听器中使用了校验(例如 @Valid)且校验失败,可能抛出。
  • o.s.messaging…​MethodArgumentTypeMismatchException:如果入站消息被转换为目标方法不接受的类型,可能抛出。例如,参数声明为 Message<Foo> 但收到的是 Message<Bar>。
  • java.lang.NoSuchMethodException:1.6.3 版本新增。
  • java.lang.ClassCastException:1.6.3 版本新增。

你可以用一个 FatalExceptionStrategy 配置此错误处理器的实例,以便用户提供自己的条件性消息拒绝规则——例如委托给 Spring Retry 的 BinaryExceptionClassifier 的实现(消息监听器与异步情形)。此外,ListenerExecutionFailedException 现在有一个 failedMessage 属性,你可以在决策中使用它。如果 FatalExceptionStrategy.isFatal() 方法返回 true,错误处理器会抛出 AmqpRejectAndDontRequeueException。默认的 FatalExceptionStrategy 在判定某个异常为致命异常时会记录一条警告消息。

从 1.6.3 版本开始,将用户异常加入致命异常列表的一个便捷方式是:继承 ConditionalRejectingErrorHandler.DefaultExceptionStrategy 并重写 isUserCauseFatal(Throwable cause) 方法,使其对致命异常返回 true。

处理 DLQ 消息的一种常见模式是为这些消息设置 time-to-live,并配置额外的 DLQ 设置,使这些消息过期后被路由回主队列进行重试。这种技术的问题在于,导致致命异常的消息会永远循环下去。从 2.1 版本开始,ConditionalRejectingErrorHandler 会在导致致命异常抛出的消息上检测 x-death 头。该消息会被记录日志并丢弃。你可以通过将 ConditionalRejectingErrorHandler 上的 discardFatalsWithXDeath 属性设置为 false 来恢复以前的行为。

important

从 2.1.9 版本开始,带有这些致命异常的消息默认会被拒绝且不再入队,即使容器确认模式为 MANUAL 也是如此。这些异常通常发生在监听器被调用之前,因此监听器没有机会确认(ack)或拒绝(nack)消息,消息会以未确认状态留在队列中。要恢复以前的行为,请将 ConditionalRejectingErrorHandler 上的 rejectManual 属性设置为 false。

从 4.1 版本开始,ConditionalRejectingErrorHandler 暴露了 stopListenerOnFatal 布尔选项,用于抛出 FatalListenerExecutionException 而不是 AmqpRejectAndDontRequeueException。监听器容器会停止进一步消费,消息会被重新入队,供目标上的其他消费者处理。