Spring Retry
在我们的业务场景中,经常要调用其他的API来获取信息,比如我们的业务场景需要依赖个人信息来处理,这个时候调用个人信息服务的API,但是由于可能同一时段多方在调用这个服务,可能该服务并发太多,没有及时响应我们的调用,我们的业务就不能执行下去,这个时候我们就需要重试机制了,当然 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
使用
- 由于LZ的项目是boot的,所以我们在使用Retry的时候需要开启Retry。加入@EnableRetry
- 接下来就是使用了,LZ使用的注解类型,所以在项目中加入注解 @Retryable
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 3000L)) private void retryService() { }
其中retryService就有了重试的功能,@Retryable 中有3个参数,
- value是 可还原的异常类型,也就是重试的异常类型。
- maxAttempts 则代表了最大的尝试次数,默认是3次。
- exclude,指定异常不重试,默认为空
- include,指定异常重试,为空时,所以异常进行重试
- backoff 则代表了延迟,默认是没有延迟的,就是失败后立即重试,当然加上延迟时间的处理方案更好,看业务场景,也可以不加括号里面的(delay = 3000L)),默认延迟1000ms.
@Backoff
- delay:指定延迟后重试
- multiplier:延迟的倍数,eg: delay=1000L,multiplier=2时,第一次重试为1秒,第二次为2秒,第三次为4秒
敲黑板:注意这里如果@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
private void recoverTest(Exception e) {
System.out.println("Recover");
}
上面的代码使用@Recover来进行重试失败后的处理,其中的参数Exception 就是我们在上面的重试代码中捕获的异常了。当重试达到指定次数后,将会回调。
这里要注意的是如果要使用@Recover,@Retryable中不可以有返回值。
拓展 说完了注解,我们来说说用API的方式实现
RetryTemplate
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
重试的时候调用
这是一个重试策略接口,而其中它的重试策略具体有下面几种
- AlwaysRetryPolicy:总是重试,直到成功为止
- CircuitBreakerRetryPolicy:熔断器策略
- CompositeRetryPolicy:组合重试策略
- ExceptionClassifierRetryPolicy:不同异常策略
- NeverRetryPolicy:只允许callback一次的策略
- SimpleRetryPolicy:简单重试策略,默认重试最大次数为3次
- TimeoutRetryPolicy:超时重试策略,默认超时时间为1
而默认的重试策略则是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
上面的是它的实现类
- ExponentialBackOffPolicy:指数回退策略,线程安全的,适合并发操作。需要设置Sleeper。其中InitialInterval(设置初始睡眠间隔值。默认值是100毫秒。不能设置为小于1的值)、MaxInterval(最大回退周期的Setter。默认是30000(30秒)。如果调用此方法的值小于1,则重置为1。设置这个值以避免无限等待如果后退了大量的次数(或者避免如果乘数被设置太高)、Multiplier(设置乘数值默认是2.0,如果小于等于1.0,则是1.0).如果不设置则使用默认值。
- NoBackOffPolicy:没有回退策略,每次立即执行
- FixedBackOffPolicy:固定时间回退策略,默认回退1000ms。
- UniformRandomBackOffPolicy:随机回退策略,默认最小时间是500ms,最大时间是1500ms。
- ExponentialRandomBackOffPolicy:随机指数退避策略
有状态重试 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