从错误和代理错误中恢复
Spring AMQP提供了一些高级特性来解决协议错误或者代理失败发生时的恢复与自动重连。我们已经在前面看到了相关组件。在这里,我们将它们总结,概括它们的特点,以及恢复场景。
主要的重连通过CachingConnectionFactory启用。使用RabbitAdmin进行自动声明也很有用处。另外,如果你比较在乎发送的质量,以也许会使用RabbitTemplate和SimpleMessageListenerContainer的channelTransacted属性,还有 SimpleMessageListenerContainer的Acknowledge.AUTO属性。
自动声明Exchange,Queue和Binding
RabbitAdmin在启动的时候会声明exchange,queues和binding。它是延迟完成的,是在ConnectionListener中,所以如果在应用启动的时候消息代理不可用也没关系。Connection被第一次使用的时候,监听器会被触发,admin这一特色会被应用。监听器中的自动声明的另外一个好处是如果连接由于某种原因断了(代理挂掉,或者网络失灵),下次连接时候它又会被启用。
注意:
以这种方式启动队列必须要有固定的名称。要么显示声明,要么通过框架为AnonymousQueues自动生成。 AnonymousQueues不可以持久化,专属的,自动删除。
重点:
自动声明只能在CachingConnectionFactory缓存模式是CHANNEL时候启用。这个限制的存在是因为专属的或者自动删除的队列和连接绑定。
同步操作失败和重试选项
如果在使用RabbitTemplate同步操作的过程中丢失了到代理的连接,Spring AMQP会抛出AmqpException(通常是AmqpIOException)。所以我们不会隐藏这一问题,所以你必须能够捕获并且响应这一异常。最简单处理方式是你认为连接断开,不是你这面的问题,重试这些操作。你可以手动的做这些,或者你可以使用Spring Retry来完成这些。
Spring Retry提供了一些AOP拦截器,重试的许多参数来简化重试。Spring AMQP也提供了一些工厂来方便Spring Retry的创建。给你提供了强类型回调接口来实现定制的恢复逻辑。参看StatefulRetryOperationsInterceptor和StatelessRetryOperationsInterceptor获得更详细的说明。如果没有事务或者事务在重试回调中,无状态重试就可以。对于存在正在进行的事务或者要进行回滚,无状态重试并不适合。在事务之间断开连接和事务回滚有着同样的效果,在这种情况下,有状态重试是最佳选择。
从1.3版本开始,一个创建器API提供来用于装配拦截器:
@Bean
public StatefulRetryOperationsInterceptor interceptor() {
return RetryInterceptorBuilder.stateful()
.maxAttempts(5)
.backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
.build();
}
只有重试的一部分功能可以通过这种方式来配置,如果需要更为高级的功能,你可以通过RetryTemplate来完成。
报文监听器和异步场景
如果MessageListener由于业务而失败而抛出异常,那么这个异常将会被消息监听器容器处理,接着它会处理另外一条消息。如果这个错误是因为连接断开导致,那么接收消息的消费者必须必须取消并且重新启动。SimpleMessageListenerContainer无缝的处理了这些,并且记录了监听器被重新启动的日志。事实上,它会进行无限的反复尝试来启动这个消费者,如果消费者行为很糟糕否则它不会放弃。一个副作用是如果代理挂掉,它会不断尝试直到连接重新建立。
业务异常的处理和协议错误,连接断开的处理不一样,需要更多的考虑或者配置,尤其是在事务或者容器应答被启用的时候。在RabbitMQ2.8.x版本以前,对于dead letter没有明确的定义,所以因为业务处理异常而导致的拒绝或者回滚将导致消息被无限的发送下去。为了限制客户端重新发送的数目,以种选择是在监听器上使用 StatefulRetryOperationsInterceptor拦截器增强。这个监听器有一个恢复回调,这个回调中实现dead letter的行为。
另外一种处理方式,是设置rejectRqueued属性为false,这将导致所有的错误消息被丢弃。当RabbitMQ的版本是2.8.x或者是更高,这将使得消息转发到Dead Letter Exchange。
或者你可以抛出AmqpRejectAndDontRequeueException,这样阻止消息被重新塞入队列,不管defaultRequeueRejected是否设置。
通常,两种技术可以结合使用。在增强链中使用StatefulRetryOperationsInterceptor,它的MessageRecover会抛出 AmqpRejectAndDontRequeueException异常。如果尝试已经耗尽 MessageRecover将会被调用。默认的 MessageRecover简单的消费错误的消息,并且释放出警告信息。在这种情况下,消息得到了应答,但是不会被发送到Dead Letter Exchange。
从1.3版本开始,RepublishMessageRecoverer被提供在重试耗尽的情况下,来重新发布消息。
@Bean
RetryOperationsInterceptor interceptor() {
return RetryInterceptorBuilder.stateless()
.withMaxAttempts(5)
.setRecoverer(newRepublishMessageRecoverer(amqpTemplate(), "bar", "baz"))
.build();
}
重试异常分类
Spring Retry拥有很大的灵活性来决定什么样的异常可以出发重试。默认的情况下,所有的异常都会出发重试。在用户异常被包装在ListenerExecutionFailedException里面的情况下,我们需要分类器检测到异常起因。默认情况下,分类器只查看顶级异常。
自从Spring1.0.3开始,分类器BinaryExceptionClassifier有一个traverseCauses属性。当设置为true时,它将横向查找匹配的异常源。
为了使用这个分类器进行重试,使用SimpleRetryPolicy,它的构造函数中有最大尝试次数,异常Map,还有 traverseCauses选项。你需要将这个策略注入到RetryTemplate中。