Spring Retry

  在我们的业务场景中,经常要调用其他的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

  使用

  • 由于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来进行重试失败后的处理,其中的参数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 

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏草根专栏

用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 + 项目准备

REST 是 Representational State Transfer 的缩写. 它是一种架构的风格, 这种风格基于一套预定义的规则, 这些规则描述了网络...

1.2K6
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第三十一天 WebService学习【悟空教程】

简单的网络应用使用单一语言写成,它的唯一外部程序就是它所依赖的数据库。大家想想是不是这样呢?

2224
来自专栏aoho求索

Spring Cloud Bus中的事件的订阅与发布(二)

在之前的文章Spring Cloud Bus中的事件的订阅与发布(一)介绍了消息总线的相关事件。本文主要介绍消息总线的事件监听器以及消息的订阅与发布。 事件监听...

4587
来自专栏时序数据库专栏

Elasticsearch 底层系列之分片恢复解析

    我们是基础架构部,腾讯云 CES/CTSDB 产品后台服务的支持团队,我们拥有专业的ES开发运维能力,为大家提供稳定、高性能的服务,欢迎有需求的童鞋接入...

1825
来自专栏Create Sun

基础拾遗------webservice详解

前言   工作当中常用的服务接口有三个wcf,webservice和webapi.首先第一个接触的就是webservice,今天大致总结一下。 1.webser...

40611
来自专栏北京马哥教育

ls 命令还能这么玩?看一下这 20 个实用范例

2024
来自专栏腾讯Bugly的专栏

美女程序媛发福利,读懂ANR的trace文件So easy

想要分析ANR问题,读懂trace文件是关键。Trace文件到底是什么鬼?如何才能破解深藏其中的奥义? App的进程发生ANR时,系统让活跃的Top进程都进行了...

4065
来自专栏北京马哥教育

惊心动魄,Linux被死锁阵痛后的破门实录

Oh, My God! 是死锁问题。尽管报错不多,对性能目前看来也无太大影响,但还是需要解决,保不齐哪天成为性能瓶颈。

1452
来自专栏SDNLAB

OpenDaylight Lithium-SR2 Cluster集群搭建

目的 希望大家能够通过本教程对OpenDaylight集群的基本概念如shard/基本配置有所了解,感受OpenDaylight的High Availabili...

3585
来自专栏编码小白

ofbiz初级教程

本教程是ofbiz 基本应用,它涵盖了OFBiz应用程序开发过程的基本原理。目标是使开发人员熟悉最佳实践,编码惯例,基本控制流程以及开发人员对OFBiz定制所需...

1.8K3

扫码关注云+社区

领取腾讯云代金券