我正在运行Spring AMQP消息侦听器。
public class ConsumerService implements MessageListener {
@Autowired
RabbitTemplate rabbitTemplate;
@Override
public void onMessage(Message message) {
try {
testService.process(message); //This process method can throw Business Exception
} catch (BusinessException e) {
//Here we can just log the exception. How the retry attempt is made?
} catch (Exception e) {
//Here we can just log the exception. How the retry attempt is made?
}
}
}
如您所见,在处理过程中可能会出现异常。我想重试因为Catch块中的特定错误。我不能通过onMessage中的异常。 如何告诉RabbitMQ有异常并重试?
答案 0 :(得分:24)
由于onMessage()
不允许抛出已检查的异常,因此可以将异常包装在RuntimeException
中并重新抛出。
try {
testService.process(message);
} catch (BusinessException e) {
throw new RuntimeException(e);
}
但请注意,这可能会导致邮件无限期地重新传递。这是如何工作的:
RabbitMQ支持拒绝邮件并要求代理重新排队。这显示为here。但RabbitMQ本身并没有重试策略的机制,例如:设置最大重试次数,延迟等
使用Spring AMQP时,“拒绝重新排队”是默认选项。当存在未处理的异常时,Spring的SimpleMessageListenerContainer
默认会执行此操作。因此,在您的情况下,您只需要重新抛出捕获的异常。但请注意,如果您无法处理消息并且始终抛出异常,则会无限期地重新传递该消息并导致无限循环。
您可以通过抛出AmqpRejectAndDontRequeueException
异常来覆盖每条消息的此行为,在这种情况下,消息不会被重新排队。
您还可以通过设置
完全关闭SimpleMessageListenerContainer
的“拒绝重新排队”行为
container.setDefaultRequeueRejected(false)
当邮件被拒绝且没有重新排队时,如果在RabbitMQ中设置了一条消息,它将丢失或转移到DLQ。
如果您需要具有最大尝试次数,延迟等的重试策略,最简单的方法是设置弹簧“无状态”RetryOperationsInterceptor
,它将在线程内进行所有重试(使用Thread.sleep()
)而不拒绝每次重试时都会显示消息(因此每次重试都不会返回RabbitMQ)。重试耗尽时,默认情况下会记录一条警告并消耗该消息。如果您要发送到DLQ,您需要RepublishMessageRecoverer
或自定义MessageRecoverer
拒绝邮件而不会重新排队(在后一种情况下,您还应setup使用RabbitMQ DLQ队列)。默认消息恢复器的示例:
container.setAdviceChain(new Advice[] {
org.springframework.amqp.rabbit.config.RetryInterceptorBuilder
.stateless()
.maxAttempts(5)
.backOffOptions(1000, 2, 5000)
.build()
});
这显然有一个缺点,即您将在整个重试期间占用线程。您还可以选择使用“有状态”RetryOperationsInterceptor
,它会在每次重试时将消息发送回RabbitMQ,但延迟仍将在应用程序中使用Thread.sleep()
实现,并设置为有状态拦截器有点复杂。
因此,如果您希望在不占用Thread
的情况下重试延迟,则需要在RabbitMQ队列上使用TTL进行更多涉及的自定义解决方案。如果你不想要指数退避(所以延迟不会在每次重试时增加),它会更简单一些。要实现这样的解决方案,您基本上在rabbitMQ上使用参数创建另一个队列:"x-message-ttl": <delay time in milliseconds>
和"x-dead-letter-exchange":"<name of the original queue>"
。然后在主队列上设置"x-dead-letter-exchange":"<name of the queue with the TTL>"
。所以现在当你拒绝并且没有重新排队消息时,RabbitMQ会将它重定向到第二个队列。当TTL过期时,它将被重定向到原始队列,从而重新传送到应用程序。所以现在你需要一个重试拦截器,在每次失败后拒绝给RabbitMQ的消息,并跟踪重试次数。为了避免在应用程序中保持状态的需要(因为如果您的应用程序是群集的,您需要复制状态),您可以从RabbitMQ设置的x-death
标头计算重试计数。查看有关此标题here的更多信息。因此,在这一点上实现自定义拦截器比使用此行为自定义Spring状态拦截器更容易。