RetryTemplate

1. 开发中接口重试的场景

日常开发中,经常会遇到这样的场景:执行一次接口调用,如RPC调用,偶现失败,原因可能是dubbo超时、连接数耗尽、http网络抖动等,出现异常时我们并不能立即知道原因并作出反应,可能只是一个普通的RpcException或RuntimeException,

对于这种小概率的异常,往往需要尝试再次调用(前提是接口是幂等的),因为由于网络问题、下游服务暂时的不稳定导致的异常,一段时间后理论上是可以自恢复的;

例如,有时候项目需要进行同步数据,一定要同步成功,不然对于业务会有影响,偶发性的会出现调用接口失败,失败并不是特别多;

一般我们处理偶发异常的流程如下:异常时,

(1)循环的进行远程调用若干次数,记录一下调用失败的记录;
(2)休眠一段时间,尝试等待下游系统自恢复或释放连接数,继续循环调用失败的请求;
(3)如果再调用失败、通过人工二次调用进行修复;

当然,你可以通过写一个指定次数的for循环来执行重试,或者在catch时再次尝试调用一次接口,这么做是可以,但是代码会显得不够优雅;通过前面描述的2种方式,思考下:

对于一个重试模块,要具备哪些属性?

(1)重试时执行的方法,这个方法执行成功后就不用再重试,这个方法要求幂等,并且失败时需要抛出异常来标识执行失败;
(2)重试策略:重试最大次数、重试最大时间即XXms后不再重试、重试次数之间的间隔、对于哪些异常才需要重试;
(3)重试次数达到上限任未成功,最后的兜底逻辑,如插入重试任务表、发送消息给系统管理员等;

2. 重试框架需要解决的问题

  1. 重试的策略(RetryPolicy
    无限重试?最多重试几次、指定的时间范围内可以重试、或者多种重试策略组合。

  2. 重试的要休眠多久(BackOffPolicy
    重试时间间隔,每次都休眠固定的时间、第一次1s 第二次2s 第三次4s 、随机的休眠时间

  3. 兜底方案(Recover)
    如果所有的重试都失败了、兜底方案是什么?有点类似限流,最差返回你系统繁忙的界面。

3. Spring Retry的基本概念

Spring Retry 是从 Spring batch 中独立出来的一个功能,主要实现了重试和熔断,对于那些重试后不会改变结果,毫无意义的操作,不建议使用重试。spring retry提供了注解和编程 两种支持,提供了 RetryTemplate 支持,类似RestTemplate。整个流程如下:

RetryTemplate_第1张图片

结合我们上面对一个重试模块应该具备哪些属性的思考,看看这几个属性的作用:

  1. RetryTemplate: 封装了Retry基本操作,是进入spring-retry框架的整体流程入口,通RetryTemplate可以指定监听、回退策略、重试策略等。

  2. RetryCallback:该接口封装了业务代码,且failback后,会再次调用RetryCallback接口,直到达到重试次数/时间上限;

  3. RecoveryCallback:当RetryCallback不能再重试的时候,如果定义了RecoveryCallback,就会调用RecoveryCallback,并以其返回结果作为最终的返回结果。此外,RetryCallback和RecoverCallback定义的接口方法都可以接收一个RetryContext上下文参数,通过它可以获取到尝试次数、异常,也可以通过其setAttribute()和getAttribute()来传递一些信息。

  4. RetryPolicy:重试策略,描述什么条件下可以尝试重复调用RetryCallback接口;策略包括最大重试次数、指定异常集合/忽略异常集合、重试允许的最大超时时间;RetryTemplate内部默认时候用的是SimpleRetryPolicy,SimpleRetryPolicy默认将对所有异常进行尝试,最多尝试3次。还有其他多种更为复杂功能更多的重试策略;

  5. BackOffPolicy:回退策略,用来定义在两次尝试之间需要间隔的时间,如固定时间间隔、递增间隔、随机间隔等;RetryTemplate内部默认使用的是NoBackOffPolicy,其在两次尝试之间不会进行任何的停顿。对于一般可重试的操作往往是基于网络进行的远程请求,它可能由于网络波动暂时不可用,如果立马进行重试它可能还是不可用,但是停顿一下,过一会再试可能它又恢复正常了,所以在RetryTemplate中使用BackOffPolicy往往是很有必要的;

  6. RetryListener:RetryTemplate中可以注册一些RetryListener,可以理解为是对重试过程中的一个增强,它可以在整个Retry前、整个Retry后和每次Retry失败时进行一些操作;如果只想关注RetryListener的某些方法,则可以选择继承RetryListenerSupport,它默认实现了RetryListener的所有方法;

4. 代码示例

实际开发中,我们一般使用简单的固定间隔和重试指定次数的策略,代码示例如下:
2.1 添加依赖

        <dependency>
            <groupId>org.springframework.retrygroupId>
            <artifactId>spring-retryartifactId>
        dependency>

2.2 配置文件

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        builder.setConnectTimeout(Duration.ofMillis(5000));
        builder.setReadTimeout(Duration.ofMillis(5000));
        RestTemplate restTemplate = builder.build();
        
        return restTemplate;
    }

    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();
        // 设置退避策略:固定间隔时间重试
        retryTemplate.setBackOffPolicy(new FixedBackOffPolicy());

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        // 配置的重试次数
        retryPolicy.setMaxAttempts(3);
        retryTemplate.setRetryPolicy(retryPolicy);
        return retryTemplate;
    }

}

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RetryTemplate retryTemplate;
   
    /**
     * 重试请求
     *
     * @param url
     * @param object
     * @return
     */
    private CommonResponse retryRequestTemplate(String url, Object object) {
        CommonResponse commonResponse = retryTemplate.execute(
                retryContext ->
                        restTemplate.postForObject(url, object, CommonResponse.class),
                retryContext -> {
                    log.error("多次调用{}接口失败,retryContext={}", url, retryContext);
                    return null;
                });
        return commonResponse;
    }

你可能感兴趣的:(工作,java)