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
认为答案已被接受(与第一个解决方案的情况相同),而且不会调用侦听器.<代码>**结论**:使用ImmediateAcknowledgeAmqpException或
ExceptionHandler`异常,我们可以完全控制永久拒绝消息(在后台确认)并重新发送到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**: Using
ImmediateAcknowledgeAmqpExceptionor
ExceptionHandler` 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.
相关文章