在我们的业务场景中,经常要调用其他的API来获取信息,比如我们的业务场景需要依赖个人信息来处理,这个时候调用个人信息服务的API,但是由于可能同一时段多方在调用这个服务,可能该服务并发太多,没有及时响应我们的调用,我们的业务就不能执行下去,这个时候我们就需要重试机制了,当然 Spring 已经给我们提供了- Retry。
Spring Retry提供了自动重新调用失败操作的功能。为了使处理更加健壮并且不易出现故障,有时它会自动重试失败的操作,以防它在后续尝试中成功。
从2.2.0开始,重试功能从Spring Batch中撤出。它现在是Spring Retry新库的一部分。
所以我们如果需要使用Retry,就需要引入Retry库了,
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
maven:
compile 'org.springframework.retry:spring-retry:1.2.2.RELEASE'
gradle:
Spring retry 的版本地址如下:https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.retry%22%20AND%20a%3A%22spring-retry%22
示例:
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 3000L))
private void retryService() {
}
其中retryService就有了重试的功能,@Retryable 中有3个参数,
@Backoff
敲黑板:注意这里如果@Retryable注解的方法是在Service层,然后在Controller层进行调用的,如果你在本类中调用,那么@Retryable 不会工作。因为当使用@Retryable时,Spring会在原始bean周围创建一个代理,然后可以在特殊情况下特殊处理,这也就是重试的原理了。所以在这种情况下,Spring推荐我们调用一个实际的方法,然后捕获我们在value中抛出的异常,然后根据@Retryable 的饿配置来进行调用。
使用了@Retryable的方法,你要把异常进行抛出处理,要不不会被Retry捕获
当然了,有了异常的捕获,就有专门针对的处理了,看代码
@Override
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000L))
public void retryTest() {
String text = null;
if (text.equals("1")) {
System.out.println(text);
}
}
上面的代码使用@Recover来进行重试失败后的处理,其中的参数Exception 就是我们在上面的重试代码中捕获的异常了。当重试达到指定次数后,将会回调。
这里要注意的是如果要使用@Recover,@Retryable中不可以有返回值。
拓展 说完了注解,我们来说说用API的方式实现
RetryTemplate是RetryOperations的实现类,实现了重试和熔断,RetryOperations的Api:
public interface RetryOperations {
/**
* Execute the supplied {@link RetryCallback} with the configured retry
* semantics. See implementations for configuration details.
*
* @return the value returned by the {@link RetryCallback} upon successful
* invocation.
* @throws E any {@link Exception} raised by the
* {@link RetryCallback} upon unsuccessful retry.
* @throws E the exception thrown
* @param <T> the return value
* @param retryCallback the {@link RetryCallback}
* @param <E> the exception to throw
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;
/**
* Execute the supplied {@link RetryCallback} with a fallback on exhausted
* retry to the {@link RecoveryCallback}. See implementations for
* configuration details.
*
* @return the value returned by the {@link RetryCallback} upon successful
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
* @throws E any {@link Exception} raised by the
* @param <T> the type to return
* @param <E> the type of the exception
* @param recoveryCallback the {@link RecoveryCallback}
* @param retryCallback the {@link RetryCallback}
* {@link RecoveryCallback} upon unsuccessful retry.
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;
/**
* A simple stateful retry. Execute the supplied {@link RetryCallback} with
* a target object for the attempt identified by the {@link DefaultRetryState}.
* Exceptions thrown by the callback are always propagated immediately so
* the state is required to be able to identify the previous attempt, if
* there is one - hence the state is required. Normal patterns would see
* this method being used inside a transaction, where the callback might
* invalidate the transaction if it fails.
*
* See implementations for configuration details.
*
* @param retryCallback the {@link RetryCallback}
* @param retryState the {@link RetryState}
* @return the value returned by the {@link RetryCallback} upon successful
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
* @throws E any {@link Exception} raised by the {@link RecoveryCallback}.
* @throws ExhaustedRetryException if the last attempt for this state has
* already been reached
* @param <T> the type of the return value
* @param <E> the type of the exception to return
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException;
/**
* A stateful retry with a recovery path. Execute the supplied
* {@link RetryCallback} with a fallback on exhausted retry to the
* {@link RecoveryCallback} and a target object for the retry attempt
* identified by the {@link DefaultRetryState}.
* @param recoveryCallback the {@link RecoveryCallback}
* @param retryState the {@link RetryState}
* @param retryCallback the {@link RetryCallback}
* @see #execute(RetryCallback, RetryState)
* @param <T> the return value type
* @param <E> the exception type
* @return the value returned by the {@link RetryCallback} upon successful
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
* @throws E any {@link Exception} raised by the {@link RecoveryCallback} upon unsuccessful retry.
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
throws E;
}
可以看出,全是execute()方法,细心的朋友会发现,每个方法都有一个RetryCallback参数
RetryCallback定义了重试的回调操作,定义好重试的操作后,就是怎么触发了重试了,重试策略就需要看RetryPolicy了。
canRetry 在每次重试的时候判断,是否需要继续
open 重试开始前调用,保存上下文信息
registerThrowable
重试的时候调用
这是一个重试策略接口,而其中它的重试策略具体有下面几种
而默认的重试策略则是SimpleRetryPlicy,注释如下:
/**
*
* Simple retry policy that retries a fixed number of times for a set of named
* exceptions (and subclasses). The number of attempts includes the initial try,
* so e.g.
*
* <pre>
* retryTemplate = new RetryTemplate(new SimpleRetryPolicy(3));
* retryTemplate.execute(callback);
* </pre>
*
* will execute the callback at least once, and as many as 3 times.
*
* @author Dave Syer
* @author Rob Harrop
* @author Gary Russell
*
*/
注释中已经写明了支持3次重试,当然如果3次重试失败,或者是需要返回值,那么我们就需要callback了。
讲完了重试策略,再来说说重试回退策略。
BackOffPolicy
上面的是它的实现类
有状态重试 OR 无状态 重试
所谓无状态重试是指重试在一个线程上下文中完成的重试,反之不在一个线程上下文完成重试的就是有状态重试。之前的SimpleRetryPolicy就属于无状态重试,因为重试是在一个循环中完成的。那么什么会后会出现或者说需要有状态重试呢?通常有两种情况:事务回滚和熔断。
数据库操作异常DataAccessException,不能执行重试,而如果抛出其他异常可以重试。
熔断的意思不在当前循环中处理重试,而是全局重试模式(不是线程上下文)。熔断会跳出循环,那么必然会丢失线程上下文的堆栈信息。那么肯定需要一种“全局模式”保存这种信息,目前的实现放在一个cache(map实现的)中,下次从缓存中获取就能继续重试了。
看示例:
public void retryTest() {
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
try {
String result = template.execute(new RetryCallback<String, Exception>() {
// 重试操作
@Override
public String doWithRetry(RetryContext retryContext) throws Exception {
return "";
}
}, new RecoveryCallback<String>() {
@Override
public String recover(RetryContext retryContext) throws Exception {
return "";
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
上述代码中,template.execute来执行一个重试操作,下面是函数定义
--------------------- 参考:https://blog.csdn.net/u011116672/article/details/77823867