有些场景需要我们对一些异常情况下面的任务进行重试,比如:调用远程的RPC服务,可能由于网络抖动出现第一次调用失败,尝试几次就可以恢复正常。
spring-retry是spring提供的一个基于spring的重试框架,非常好用。
官网地址: https://github.com/spring-projects/spring-retry
下面是springboot调用spring-retry:
配置依赖Maven:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
启动类:
@SpringBootApplication @EnableTransactionManagement // 启注解事务管理 @EnableScheduling @EnableRetry // 【注意这里】启用了重试功能 public class TestWebApplication { public static void main(String[] args) { SpringApplication.run(TestWebApplication.class, args); } }
重试部分的代码:
@Service public class RetryServiceImpl implements RetryService { private static final Logger LOGGER = LoggerFactory.getLogger(RetryServiceImpl.class); private AtomicInteger count = new AtomicInteger(1); @Override @Retryable(value = { RemoteAccessException.class }, maxAttemptsExpression = "${retry.maxAttempts:10}", backoff = @Backoff(delayExpression = "${retry.backoff:1000}")) public void retry() { LOGGER.info("start to retry : " + count.getAndIncrement()); throw new RemoteAccessException("here " + count.get()); } @Recover public void recover(RemoteAccessException t) { LOGGER.info("SampleRetryService.recover:{}", t.getClass().getName()); } }
【注意】@Recover 的用法。它要求它注释的方法的返回值必须和@Retryable的注释的方法返回值保持一致,否则@Recover 注释的方法不会被调用。它还有关于自己参数的使用要求。
更详细的@Recover 的使用说明,参考它的官网Javadoc,如下:
org.springframework.retry.annotation.Recover @Import(value={RetryConfiguration.class}) @Target(value={METHOD, TYPE}) @Retention(value=RUNTIME) @Documented Annotation for a method invocation that is a recovery handler. A suitable recovery handler has a first parameter of type Throwable (or a subtype of Throwable) and a return value of the same type as the @Retryable method to recover from. The Throwable first argument is optional (but a method without it will only be called if no others match). Subsequent arguments are populated from the argument list of the failed method in order. Since: 2.0 Author: Dave Syer
还可以自己写代码的方法定制化retry的使用,如下:
public static void main(String[] args) throws Exception { // 重试策略 SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(5); // 设置间隔策略 FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); backOffPolicy.setBackOffPeriod(1000L); // 初始化重试模板 RetryTemplate template = new RetryTemplate(); template.setRetryPolicy(retryPolicy); template.setBackOffPolicy(backOffPolicy); AtomicInteger count = new AtomicInteger(); String result = template.execute(context -> { System.out.println("TestAll.main() " + count.incrementAndGet()); throw new IllegalArgumentException(); }, context -> { System.out.println("TestAll.main() recover"); return "world"; }); System.out.println("result:" + result); }
【注意】
interface RetryPolicy 它有很多的实现类可以使用。
下面是官网的原始内容:
This project provides declarative retry support for Spring applications. It is used in Spring Batch, Spring Integration, Spring for Apache Hadoop (amongst others).
Quick Start
Example:
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
Call the "service" method and if it fails with a RemoteAccessException
then it will retry (up to three times by default), and then execute the "recover" method if unsuccessful. There are various options in the @Retryable
annotation attributes for including and excluding exception types, limiting the number of retries and the policy for backoff.
Building
Requires Java 1.7 and Maven 3.0.5 (or greater)
$ mvn install
Features and API
RetryTemplate
To make processing more robust and less prone to failure, sometimes it helps to automatically retry a failed operation in case it might succeed on a subsequent attempt. Errors that are susceptible to this kind of treatment are transient in nature. For example a remote call to a web service or RMI service that fails because of a network glitch or a DeadLockLoserException
in a database update may resolve themselves after a short wait. To automate the retry of such operations Spring Retry has the RetryOperations
strategy. The RetryOperations
interface looks like this:
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)
throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
throws Exception, ExhaustedRetryException;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception;
}
The basic callback is a simple interface that allows you to insert some business logic to be retried:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
The callback is executed and if it fails (by throwing an Exception
), it will be retried until either it is successful, or the implementation decides to abort. There are a number of overloaded execute methods in the RetryOperations
interface dealing with various use cases for recovery when all retry attempts are exhausted, and also with retry state, which allows clients and implementations to store information between calls (more on this later).
The simplest general purpose implementation of RetryOperations
is RetryTemplate
. It could be used like this
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});
In the example we execute a web service call and return the result to the user. If that call fails then it is retried until a timeout is reached.
RetryContext
The method parameter for the RetryCallback
is a RetryContext
. Many callbacks will simply ignore the context, but if necessary it can be used as an attribute bag to store data for the duration of the iteration.
A RetryContext
will have a parent context if there is a nested retry in progress in the same thread. The parent context is occasionally useful for storing data that need to be shared between calls to execute.
RecoveryCallback
When a retry is exhausted the RetryOperations
can pass control to a different callback, the RecoveryCallback
. To use this feature clients just pass in the callbacks together to the same method, for example:
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
},
new RecoveryCallback<Foo>() {
Foo recover(RetryContext context) throws Exception {
// recover logic here
}
});
If the business logic does not succeed before the template decides to abort, then the client is given the chance to do some alternate processing through the recovery callback.
Stateless Retry
In the simplest case, a retry is just a while loop: the RetryTemplate
can just keep trying until it either succeeds or fails. The RetryContext
contains some state to determine whether to retry or abort, but this state is on the stack and there is no need to store it anywhere globally, so we call this stateless retry. The distinction between stateless and stateful retry is contained in the implementation of the RetryPolicy
(the RetryTemplate
can handle both). In a stateless retry, the callback is always executed in the same thread on retry as when it failed.
Stateful Retry
Where the failure has caused a transactional resource to become invalid, there are some special considerations. This does not apply to a simple remote call because there is no transactional resource (usually), but it does sometimes apply to a database update, especially when using Hibernate. In this case it only makes sense to rethrow the exception that called the failure immediately so that the transaction can roll back and we can start a new valid one.
In these cases a stateless retry is not good enough because the re-throw and roll back necessarily involve leaving the RetryOperations.execute()
method and potentially losing the context that was on the stack. To avoid losing it we have to introduce a storage strategy to lift it off the stack and put it (at a minimum) in heap storage. For this purpose Spring Retry provides a storage strategy RetryContextCache
which can be injected into the RetryTemplate
. The default implementation of the RetryContextCache
is in memory, using a simple Map
. It has a strictly enforced maximum capacity, to avoid memory leaks, but it doesn't have any advanced cache features like time to live. You should consider injecting a Map
that had those features if you need them. Advanced usage with multiple processes in a clustered environment might also consider implementing the RetryContextCache
with a cluster cache of some sort (though, even in a clustered environment this might be overkill).
Part of the responsibility of the RetryOperations
is to recognize the failed operations when they come back in a new execution (and usually wrapped in a new transaction). To facilitate this, Spring Retry provides the RetryState
abstraction. This works in conjunction with a special execute methods in the RetryOperations
.
The way the failed operations are recognized is by identifying the state across multiple invocations of the retry. To identify the state, the user can provide an RetryState
object that is responsible for returning a unique key identifying the item. The identifier is used as a key in the RetryContextCache
.
Warning: Be very careful with the implementation of
Object.equals()
andObject.hashCode()
in the key returned byRetryState
. The best advice is to use a business key to identify the items. In the case of a JMS message the message ID can be used.
When the retry is exhausted there is also the option to handle the failed item in a different way, instead of calling the RetryCallback
(which is presumed now to be likely to fail). Just like in the stateless case, this option is provided by the RecoveryCallback
, which can be provided by passing it in to the execute method of RetryOperations
.
The decision to retry or not is actually delegated to a regular RetryPolicy
, so the usual concerns about limits and timeouts can be injected there (see below).
Retry Policies
Inside a RetryTemplate
the decision to retry or fail in the execute method is determined by a RetryPolicy
which is also a factory for the RetryContext
. The RetryTemplate
has the responsibility to use the current policy to create a RetryContext
and pass that in to the RetryCallback
at every attempt. After a callback fails the RetryTemplate
has to make a call to the RetryPolicy
to ask it to update its state (which will be stored in the RetryContext
), and then it asks the policy if another attempt can be made. If another attempt cannot be made (e.g. a limit is reached or a timeout is detected) then the policy is also responsible for identifying the exhausted state, but not for handling the exception. The RetryTemplate
will throw the original exception, except in the stateful case, when no recover is available, in which case it throws RetryExhaustedException
. You can also set a flag in the RetryTemplate
to have it unconditionally throw the original exception from the callback (i.e. from user code) instead.
Tip: Failures are inherently either retryable or not - if the same exception is always going to be thrown from the business logic, it doesn't help to retry it. So don't retry on all exception types - try to focus on only those exceptions that you expect to be retryable. It's not usually harmful to the business logic to retry more aggressively, but it's wasteful because if a failure is deterministic there will be time spent retrying something that you know in advance is fatal.
Spring Retry provides some simple general purpose implementations of stateless RetryPolicy, for example a SimpleRetryPolicy
, and the TimeoutRetryPolicy
used in the example above.
The SimpleRetryPolicy
just allows a retry on any of a named list of exception types, up to a fixed number of times:
// Set the max attempts including the initial attempt before retrying
// and retry on all exceptions (this is the default):
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));
// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
}
});
There is also a more flexible implementation called ExceptionClassifierRetryPolicy
, which allows the user to configure different retry behavior for an arbitrary set of exception types though the ExceptionClassifier
abstraction. The policy works by calling on the classifier to convert an exception into a delegate RetryPolicy, so for example, one exception type can be retried more times before failure than another by mapping it to a different policy.
Users might need to implement their own retry policies for more customized decisions. For instance, if there is a well-known, solution-specific, classification of exceptions into retryable and not retryable.
Backoff Policies
When retrying after a transient failure it often helps to wait a bit before trying again, because usually the failure is caused by some problem that will only be resolved by waiting. If a RetryCallback
fails, the RetryTemplate
can pause execution according to the BackoffPolicy
in place.
public interface BackoffPolicy {
BackOffContext start(RetryContext context);
void backOff(BackOffContext backOffContext)
throws BackOffInterruptedException;
}
A BackoffPolicy
is free to implement the backOff in any way it chooses. The policies provided by Spring Retry out of the box all use Object.wait()
. A common use case is to backoff with an exponentially increasing wait period, to avoid two retries getting into lock step and both failing - this is a lesson learned from the ethernet. For this purpose Spring Retry provides the ExponentialBackoffPolicy
. There are also randomized versions delay policies that are quite useful to avoid resonating between related failures in a complex system.
Listeners
Often it is useful to be able to receive additional callbacks for cross cutting concerns across a number of different retries. For this purpose Spring Retry provides the RetryListener
interface. The RetryTemplate
allows users to register RetryListeners, and they will be given callbacks with the RetryContext
and Throwable
where available during the iteration.
The interface looks like this:
public interface RetryListener {
void open(RetryContext context, RetryCallback<T> callback);
void onError(RetryContext context, RetryCallback<T> callback, Throwable e);
void close(RetryContext context, RetryCallback<T> callback, Throwable e);
}
The open and close callbacks come before and after the entire retry in the simplest case and onError
applies to the individual RetryCallback
calls. The close method might also receive a Throwable
; if there has been an error it is the last one thrown by the RetryCallback
.
Note that when there is more than one listener, they are in a list, so there is an order. In this case open will be called in the same order while onError and close will be called in reverse order.
Declarative Retry
Sometimes there is some business processing that you know you want to retry every time it happens. The classic example of this is the remote service call. Spring Retry provides an AOP interceptor that wraps a method call in a RetryOperations
for just this purpose. The RetryOperationsInterceptor
executes the intercepted method and retries on failure according to the RetryPolicy
in the provided RepeatTemplate
.
Java Configuration for Retry Proxies
Add the @EnableRetry
annotation to one of your @Configuration
classes and use @Retryable
on the methods (or type level for all methods) that you want to retry. You can also specify any number of retry listeners. Example
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
@Bean public RetryListener retryListener1() {
return new RetryListener() {...}
}
@Bean public RetryListener retryListener2() {
return new RetryListener() {...}
}
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public service() {
// ... do something
}
}
Attributes of @Retryable
can be used to control the RetryPolicy
and BackoffPolicy
, e.g.
@Service
class Service {
@Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
public service() {
// ... do something
}
}
for a random backoff between 100 and 500 milliseconds and up to 12 attempts. There is also a stateful
attribute (default false) to control whether the retry is stateful or not. To use stateful retry the intercepted method has to have arguments, since they are used to construct the cache key for the state.
The @EnableRetry
annotation also looks for beans of type Sleeper
and other strategies used in the RetryTemplate
and interceptors to control the beviour of the retry at runtime.
The @EnableRetry
annotation creates proxies for @Retryable
beans, and the proxies (so the bean instances in the application) have the Retryable
interface added to them. This is purely a marker interface, but might be useful for other tools looking to apply retry advice (they should usually not bother if the bean already implements Retryable
).
Recovery method can be supplied, in case you want to take an alternative code path when the retry is exhausted. Methods should be declared in the same class as the @Retryable
and marked @Recover
. The return type must match the @Retryable
method. The arguments for the recovery method can optionally include the exception that was thrown, and also optionally the arguments passed to the orginal retryable method (or a partial list of them as long as none are omitted). Example:
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service(String str1, String str2) {
// ... do something
}
@Recover
public void recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
}
Version 1.2 introduces the ability to use expressions for certain properties:
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
public void service1() {
...
}
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
public void service2() {
...
}
@Retryable(exceptionExpression="#{@exceptionChecker.shouldRetry(#root)}",
maxAttemptsExpression = "#{@integerFiveBean}",
backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}"))
public void service3() {
...
}
These use the familier Spring SpEL expression syntax (#{...}
).
Expressions can contain property placeholders such as #{${max.delay}}
or #{@exceptionChecker.${retry.method}(#root)}
-
exceptionExpression
is evaluated against the thrown exception as the#root
object. -
maxAttemptsExpression
and the@BackOff
expression attributes are evaluated once, during initialization; there is no root object for the evaluation but they can reference other beans in the context.
XML Configuration
Here is an example of declarative iteration using Spring AOP to repeat a service call to a method called remoteCall
(for more detail on how to configure AOP interceptors see the Spring User Guide):
<aop:config>
<aop:pointcut id="transactional"
expression="execution(* com..*Service.remoteCall(..))" />
<aop:advisor pointcut-ref="transactional"
advice-ref="retryAdvice" order="-1"/>
</aop:config>
<bean id="retryAdvice"
class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>
The example above uses a default RetryTemplate
inside the interceptor. To change the policies or listeners, you only need to inject an instance of RetryTemplate
into the interceptor.
Contributing
Spring Retry is released under the non-restrictive Apache 2.0 license, and follows a very standard Github development process, using Github tracker for issues and merging pull requests into master. If you want to contribute even something trivial please do not hesitate, but follow the guidelines below.
Before we accept a non-trivial patch or pull request we will need you to sign the https://cla.pivotal.io/[contributor'sagreement]. Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests.
Code of Conduct
This project adheres to the Contributor Covenant. By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
相关推荐
Guava Retry的使用相对简单,你只需创建一个`RetryerBuilder`,通过链式调用来设置重试条件、间隔策略、停止策略等,最后构建并执行`Retryer`。 Guava Retry与Spring Retry的区别主要体现在以下几个方面: 1. **...
在分布式系统中,由于网络延迟、瞬时故障等原因,操作可能会失败,Spring Retry提供了一种声明式的方法来定义哪些方法应该被重试,以及在什么条件下重试。其主要特性包括: - 声明式重试:通过`@Retryable`注解,...
本文将深入探讨Spring Retry的核心概念、使用方法以及其在实际应用中的价值。 一、Spring Retry简介 Spring Retry项目是Spring框架的一个扩展,它的主要目标是简化应用程序中对重试逻辑的实现。通过使用该库,...
Spring Retry 为开发者提供了一套强大的工具,使得在 Spring Boot 应用中实现重试逻辑变得简单。结合适当的配置和业务逻辑,我们可以有效地处理网络抖动、服务不稳定等问题,提升系统的整体性能。
本文将详细阐述Spring-retry 1.1.4的重试功能及其核心概念、配置和使用方法。 一、核心概念 1. **RetryTemplate**:这是Spring-retry的核心类,用于定义和执行重试逻辑。通过这个模板,我们可以设置重试策略、回退...
Spring Retry 简单使用方法 Spring Retry 是 Spring 框架中的一种 retry 机制,主要用于解决分布式系统中数据一致性的问题。它可以在 RPC 调用或发送 MQ 消息时,自动重试失败的操作,以确保数据的一致性。 添加 ...
在这个例子中,`doSomething()` 方法将被Spring Retry包装,如果在执行过程中抛出了`MyRetryableException`,则会按照预设的重试策略进行重试。如果所有重试都失败,将抛出`RetriesExhaustedException`,通常这表示...
主要介绍了Spring boot使用spring retry重试机制的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
3. **spring-retry-1.1.2.RELEASE.jar**:Spring Retry库是Spring的一个扩展,用于处理可能出现的重试需求。在RabbitMQ中,由于网络问题或暂时的服务不可用,消息的发送和接收可能会失败。Spring Retry提供了一种...
Spring-Retry更适合已经使用Spring框架的项目,因为它提供了声明式重试,集成简单,但在定制性上可能略逊于Guava Retrying。而Guava Retrying则适用于对重试逻辑有高度定制需求的场景,但可能需要更多的编码工作。 ...
Spring Retry 是一个由 Spring 社区开发的库,专门用于处理在执行业务逻辑时可能出现的临时性错误。它提供了一种优雅的方式来实现重试机制,避免因为网络抖动、短暂的服务不可用等问题导致的异常情况。这个库尤其...
在`spring-amqp`中,你可以使用`RabbitTemplate`作为主要的工具类,它封装了发送和接收消息的方法,支持各种消息模式,如简单、Direct、Topic、Fanout等。此外,`AmqpAdmin`接口提供了声明交换机、队列和绑定的功能...
Spring-Batch 是一个开源框架,专门用于处理批量处理任务,...通过阅读提供的《spring-batch-docs.pdf》文档,可以深入了解Spring-Batch 1.0的用法和最佳实践,这对于理解和掌握Spring-Batch的后续版本也有很大的帮助。
下面的示例演示如何以声明式样式使用Spring Retry: @Configuration @EnableRetry public class Application { @Bean public Service service () { return new Service (); } } @Service class Service { @...
Spring提供了多种方式来实现重试机制,本文将介绍使用Spring Retry实现重试机制的方法。 首先,需要在项目中添加Spring Retry的依赖项。可以在pom.xml文件中添加以下依赖项: ``` <groupId>org.springframework....
文档中阐述了如何配置Resilience4J和Spring Retry作为断路器,并且介绍了断路器的工作原理和自定义配置方法。 ### Spring Boot Cloud CLI Spring Boot Cloud CLI部分指导用户如何在本地安装和使用CLI工具,运行...
Spring Retry是一个轻量级的库,它为Java和Spring应用程序提供了简单的API来处理重试逻辑。核心概念包括: 1. **RetryTemplate**: 这是Spring Retry的主要入口点,允许开发者定义重试策略并执行可重试的操作。通过...
Spring AMQP 提供了许多高级特性,如 DLQ(Dead Letter Queue)、TTL(Time To Live)、Retry Policy 和 Confirm/CNACK 机制等,可以根据实际需求进行配置和使用。 通过以上步骤,你就成功地在 Spring Boot 项目中...