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

我确实阅读了此参考:https://www.rabbitmq.com/dlx.html,但它并没有解决我的疑问,即:
如果接受消息没有问题 - spring-rabbitmq 发送确认并且一切都很好,DLX 不知道确认的消息.

I did read this reference: https://www.rabbitmq.com/dlx.html, however it doesn't resolve my doubts, namely:
In case of accepting message there is no problem - spring-rabbitmq send ack and everthing is fine, DLX doesn't know about acked message.

问题是如果拒绝回答,即抛出 MessageConverterException 怎么办?此消息已删除或移至 DLX?

The problem is in case rejecting answer, namely what about throwing MessageConverterException ? This message is removed or moved to DLX?

如果有其他异常怎么办?例如 Exception ?它被删除/重新排队/移动到 DLX ?

And what about in case other exception ? For example Exception ? It is removed/requeued/moved to DLX ?

@Gary 回答后编辑
我认为,在回答@Gary 之后,我应该添加更多关于我的案例的详细信息以及@Gary 回答的一些摘要.@Gary 完全掌握了我的用例.

Edit after answer of @Gary
I think, that after answer's @Gary I should add more details about my case and some summary of @Gary's answer. @Gary exactly grasped my use case.

我不喜欢重新排队 - 从不(我害怕循环),但我不希望在抛出异常时丢失消息(例如失去与数据库的连接) - 此消息应该重新发送到 DLX.另一方面,消息的转换应该被视为致命错误 - 没有重新排队,没有重新发送到 DLX - 只是永久删除消息.通常,取决于异常拒绝(=重新发送到 DLX,如果已配置)或接受,从不重新排队.

I wouldn't like requeue - never (I am afraid of looping), but I wouldn't like to lose messages when an exception was thrown (for example lost connection to database) - this message should be resend to DLX. On the other hand, conversion of message should be treated as fatal error - no requeue, no resend to DLX - simply permanent removing message. Generally, in depends on exception either reject (=resend to DLX if configured) or accept, never requeue.

简而言之,@Gary 提出的方法.
首先:我们可以重写ExceptionHandler来管理发送nack/ack,这给了我们一个完全的控制权.
第二:IMO更简单,解决方案是设置defaultRequeueRejected=false并在转换器中抛出ImmediateAcknowledgeAmqpException.它使 RabbitMQ 认为答案已被接受(与第一个解决方案的情况相同),而且不会调用侦听器.<代码>**结论**:使用
ImmediateAcknowledgeAmqpExceptionExceptionHandler`异常,我们可以完全控制永久拒绝消息(在后台确认)并重新发送到DLX.

To sum up in a nutshell approach proposed by @Gary.
First: We can override ExceptionHandler to manage of sending nack/ack, which gives to us a full control.
Second: IMO simpler, solution is to set defaultRequeueRejected=false and in converter throw ImmediateAcknowledgeAmqpException. It makes that RabbitMQ think that answer was accepted (the same thing as in case of first solution), moreover listener wouldn't be invoked. **Conclusion**: UsingImmediateAcknowledgeAmqpExceptionorExceptionHandler` exception we have a full control on permanent rejecting message (under hood ack) and resending to DLX.

推荐答案

RabbitMQ 对异常一无所知.

RabbitMQ knows nothing about the exceptions.

当容器捕获到异常时,它会调用 channel.basicReject(deliveryTag, requeue).

When the container catches an exception it calls channel.basicReject(deliveryTag, requeue).

如果 requeue 为真,则消息被重新排队.

If requeue is true, the message is requeued.

默认情况下,对于那些提到的以外的任何异常 这里

By default, for any exception other than those mentioned here

o.s.amqp...MessageConversionException

o.s.amqp...MessageConversionException

o.s.messaging...MessageConversionException

o.s.messaging...MessageConversionException

o.s.messaging...MethodArgumentNotValidException

o.s.messaging...MethodArgumentNotValidException

o.s.messaging...MethodArgumentTypeMismatchException

o.s.messaging...MethodArgumentTypeMismatchException

java.lang.NoSuchMethodException

java.lang.NoSuchMethodException

java.lang.ClassCastException

java.lang.ClassCastException

requeue 设置为 true,因此消息被重新排队.

requeue is set to true, so the message is requeued.

对于那些例外,传递被认为是致命的并且消息不会重新排队,如果配置了一个,它将转到 DLX/DLQ.

For those exceptions, the delivery is considered fatal and the message is NOT requeued, it will go to a DLX/DLQ if one is configured.

容器有一个标志defaultRequeueRejected,默认为true;如果您将其设置为 false;不会重新排队任何异常.

The container has a flag defaultRequeueRejected which is true by default; if you set it to false; no exceptions will be requeued.

对于应用程序级别的异常,通常会重新排队消息.要动态拒绝(而不是重新排队)消息,请确保原因链中存在 AmqpRejectAndDontRequeueException.这指示容器不要重新排队消息,它将转到 DLX/DLQ(如果已配置).此行为由上面提到的 defaultRequeueRejected 标志启用.

For application-level exceptions, generally, messages will be requeued. To dynamically reject (and not requeue) a message, make sure there is an AmqpRejectAndDontRequeueException in the cause chain. This instructs the container to not requeue the message, and it will go to the DLX/DLQ (if configured). This behavior is enabled by the defaultRequeueRejected flag mentioned above.

这一切都在文档中进行了解释,正如我在其他答案中所讨论的那样,您可以使用自定义错误处理程序来更改此行为;文档中也对此进行了说明.

This is all explained in the documentation and, as I have discussed in other answers to you, you can change this behavior by using a custom error handler; that, too, is explained in the documentation.

无法将某些异常发送到 DLX/DLQ 而不是其他;rabbit 只有一个二元选项,requeue 或 don't requeue,对于后者,如果配置了 DLX/DLQ,所有此类被拒绝的消息都会转到 DLX/DLQ.

It is not possible to send some exceptions to the DLX/DLQ and not others; rabbit only has a binary option, requeue or don't requeue and, for the latter, if a DLX/DLQ is configured all such rejected messages go to the DLX/DLQ.

Spring AMQP 提供了另一种异常,ImmediateAcknowledgeAmqpException.如果您的侦听器抛出此异常,则消息将被确认,就好像它已成功处理一样 (channel.basicAck()).这是容器提供的唯一技术,可以丢弃坏消息而不将其发送到 DLX/DLQ.

Spring AMQP provides one more exception, ImmediateAcknowledgeAmqpException. If your listener throws this exception, the message will be ack'd as if it was processed successfully (channel.basicAck()). That is the only technique provided by the container, to discard a bad message without sending it to the DLX/DLQ.

当然,您的应用程序本身可以丢弃此类消息.

Of course, your application can drop such messsages itself.

如果要DLX/DLQ所有业务异常但丢弃转换异常,抛出AmqpRejectAndDontRequeueException(或设置defaultRequeueRejected为false),并抛出ImmediateAcknowledgeAmqpException来自您的转换器.

If you want to DLX/DLQ all business exceptions but drop conversion exceptions, throw AmqpRejectAndDontRequeueException (or set defaultRequeueRejected to false), and throw ImmediateAcknowledgeAmqpException from your converter.

相关文章