前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Retry

Spring Retry

作者头像
haoming1100
发布2018-12-13 09:51:42
2.3K0
发布2018-12-13 09:51:42
举报
文章被收录于专栏:步履前行步履前行

  在我们的业务场景中,经常要调用其他的API来获取信息,比如我们的业务场景需要依赖个人信息来处理,这个时候调用个人信息服务的API,但是由于可能同一时段多方在调用这个服务,可能该服务并发太多,没有及时响应我们的调用,我们的业务就不能执行下去,这个时候我们就需要重试机制了,当然 Spring 已经给我们提供了- Retry。  

  概述

    Spring Retry提供了自动重新调用失败操作的功能。为了使处理更加健壮并且不易出现故障,有时它会自动重试失败的操作,以防它在后续尝试中成功。

  引入依赖

  从2.2.0开始,重试功能从Spring Batch中撤出。它现在是Spring Retry新库的一部分。

  所以我们如果需要使用Retry,就需要引入Retry库了,

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.2.2.RELEASE</version>
</dependency>

maven:

代码语言:javascript
复制
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
代码语言:javascript
复制
@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捕获


  当然了,有了异常的捕获,就有专门针对的处理了,看代码

代码语言:javascript
复制
   @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

  RetryTemplate是RetryOperations的实现类,实现了重试和熔断,RetryOperations的Api:

代码语言:javascript
复制
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,注释如下:

代码语言:javascript
复制
/**
 *
 * 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实现的)中,下次从缓存中获取就能继续重试了。

看示例:

代码语言:javascript
复制
  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 

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-11-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  •   概述
  •   引入依赖
  •   使用
  •   RetryTemplate
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档