重试框架spring-retry的探索与揭秘

118 阅读2分钟

重试框架spring-retry的探索与揭秘

1、前言

     在项目中,我们经常会遇到网络波动,或者调用第三方接口偶尔异常的情况。为了保证数据的可用性、程序的健壮性。我们常常加入重试的操作,多请求几次,若干次请求之后,如果还是失败,才算是真正的失败。成熟的框架上都有体现,比如dubbo的重试机制,spring全家桶自带的重试、以及google出品的guava里的重试机制等。今天以Spring框架中的重试机制为例分析、学习。

2、接入依赖

这里的依赖引用的是目前最新的版本,和老版本里的使用稍微有点差异,后面会详细说明。

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.1</version>
</dependency>

3、初体验

@Test
public void retry01(){
    RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(3).fixedBackoff(2000).build();
    retryTemplate.execute(new RetryCallback(){
        @Override
        public Object doWithRetry(RetryContext context) throws Throwable {
            System.out.println(">>>>>>>>>>>>>>>>>retry test"+ DateUtils.formatDate(new Date()));
            throw new RuntimeException("retry excption");
        }
    }, new RecoveryCallback(){
        @Override
        public Object recover(RetryContext context) throws Exception {
            System.out.println(">>>>>>>>>>>>>>>>>retry test fail");
            return null;
        }
    });
    System.out.println("over");
}

4、结果

5、详细分析

     上述demo中,实现了每隔2s,重试一次,重试三次后再执行后处理(“retry test fail”),最终结束程序的运行。

RetryTemplate:通过重试模板构建一个指定的模板,通过设置请求的次数(maxAttempts),设置时间间 隔(fixedBackoff).

    通过excute方法,并传入参数执行方法。

RetryCallback:重试回调接口。指重复请求的业务逻辑需要实现RetryCallback.doWithRetry()方法。

RetryContext: 执行重试过程的上下文。
RecoveryCallback:重试结束依然没有想要的结果,需要处理的业务接口。同样需要实现RecoveryCallback.recover() 方法。

那么问题来了,什么情况下,才能触发重试机制呢???

是异常,源码里给了注释:

追踪源码:

从源码中可以看到,他是通过传递的重试策略,判断是否可以重试。该案例中没有设置重试策略,故使用的是默认的重试策略(SimpleRetryPolicy)。

从spring框中可以看出,提供了11种重试策略:

默认的重试策略对于canRetry()的逻辑是这样的:

从这里我们就可看出是通过异常触发重试策略的。

RetryTemplate 默认提供了简单测重试机制,为了满足多变的业务场景,我们可以定制属于自己的重试框架,比如监听器,不同的重试策略等。

6、扩展

Google提供的重试框架更灵活一些、更轻量级一些。可以支持线程池、以及多种条件触发重试机制。这里有一个小demo,就不做源码的分析了。
@Test
public void retry02() {
    Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
            .retryIfResult(Predicates.equalTo(false))
            .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .build();


    try {
        RetryerCallable<Boolean> wrap = retryer.wrap(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                System.out.println("dosomething>>>>>>>>>:" + DateUtils.formatDate(new Date()));
                return false;
            }
        });

        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<Boolean> submit = executorService.submit(wrap);
        submit.get();
    }catch (Exception e){
        System.out.println("opver");
    }

}