Spring-Retry

关于重试

  开发中经常要调用其他项目提供的Api,这些Api可能是同公司其他团队或其他公司提供的,所以避免不了会出现网络抖动调用失败的情况,这种情况下往往重试一次就正常了。对于add或update操作,如果是非幂等性操作,要谨慎使用,重试可能会造成业务异常。

可以使用apache HttpClient 或自己实现,spring 也提供了重试机制 Spring Retry。

GitHub地址: Spring-Retry

有两种使用方式,基于编码方式和基于申明式,下面是常用类图:

常用类图结构

补偿/回退 策略

Spring-Retry

重试策略

Spring-Retry

重试上下文缓存(强引用 与 软引用 缓存方式)

Spring-Retry

重试监听者(实现不同阶段的通知功能)

Spring-Retry

引入依赖

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

编码方式

RetryTemplate配置

    @Bean(name = "retryTemplate")
    public RetryTemplate getRetryTemplate() {
        RetryTemplate template = new RetryTemplate();

        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        //设置2s重试一次
        backOffPolicy.setBackOffPeriod(2000);
        template.setBackOffPolicy(backOffPolicy);

        SoftReferenceMapRetryContextCache contextCache = new SoftReferenceMapRetryContextCache();
        template.setRetryContextCache(contextCache);

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        template.setRetryPolicy(retryPolicy);

        template.setThrowLastExceptionOnExhausted(true);
        return template;
    }

不指定 RecoveryCallback 

    @Autowired RetryTemplate retryTemplate;
@RequestMapping(value = "/test", method = { RequestMethod.GET }) public void test() { Boolean execute = retryTemplate.execute(ctx -> testMethod("arg-1", 12)); System.out.println("result : " + execute); } public Boolean testMethod(String arg1, Integer arg2) throws RuntimeException { logger.info("调用 testMethod 方法:arg1:" + arg1 + ",arg2:" + arg2); throw new RuntimeException("出错啦!"); }

执行结果:

2019-08-03 18:23:19.557 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:65] 调用 testMethod 方法:arg1:arg-1,arg2:12
2019-08-03 18:23:21.563 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:65] 调用 testMethod 方法:arg1:arg-1,arg2:12
2019-08-03 18:23:23.563 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:65] 调用 testMethod 方法:arg1:arg-1,arg2:12
2019-08-03 18:23:23.567 [ERROR][http-nio-8097-exec-1]:com.demo.CommonExceptionAdvice [processUnauthenticatedException:45] 
java.lang.RuntimeException: 出错啦!

指定 RecoveryCallback 

    @RequestMapping(value = "/test", method = { RequestMethod.GET })
    public void test() {
        Boolean execute = retryTemplate.execute(ctx -> testMethod("arg-1", 12), ctx -> callBack("arg-1", 12));
        System.out.println("result : " + execute);
    }

    public Boolean testMethod(String arg1, Integer arg2) throws RuntimeException {
        logger.info("调用 testMethod 方法:arg1:" + arg1 + ",arg2:" + arg2);
        throw new RuntimeException("出错啦!");
    }

    public Boolean callBack(String arg1, Integer arg2) throws RuntimeException {
        logger.info("调用 callBack 方法:arg1:" + arg1 + ",arg2:" + arg2);
        return false;
    }

返回结果: 

2019-08-03 18:39:23.777 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:63] 调用 testMethod 方法:arg1:arg-1,arg2:12
2019-08-03 18:39:25.781 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:63] 调用 testMethod 方法:arg1:arg-1,arg2:12
2019-08-03 18:39:27.781 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:63] 调用 testMethod 方法:arg1:arg-1,arg2:12
2019-08-03 18:39:27.781 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [callBack:68] 调用 callBack 方法:arg1:arg-1,arg2:12
result : false 

注解申明

@Retryable注解 

注解参数:

value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有 

Spring-Retry
    /**
     * Exception types that are retryable. Synonym for includes(). Defaults to empty (and
     * if excludes is also empty all exceptions are retried).
     * @return exception types to retry
     */
    Class<? extends Throwable>[] value() default {};

    /**
     * Exception types that are retryable. Defaults to empty (and if excludes is also
     * empty all exceptions are retried).
     * @return exception types to retry
     */
    Class<? extends Throwable>[] include() default {};

    /**
     * Exception types that are not retryable. Defaults to empty (and if includes is also
     * empty all exceptions are retried).
     * @return exception types to retry
     */
    Class<? extends Throwable>[] exclude() default {};

    /**
     * @return the maximum number of attempts (including the first failure), defaults to 3
     */
    int maxAttempts() default 3;

    /**
     * Specify the backoff properties for retrying this operation. The default is no
     * backoff, but it can be a good idea to pause between attempts (even at the cost of
     * blocking a thread).
     * @return a backoff specification
     */
    Backoff backoff() default @Backoff();
View Code

@Backoff

value:和delay一样,在指数级情况下用作初始值
maxDelay:两次重试之间的最大时间如果小于delay,则忽略
multiplier:如果为正数,用来作为生成下一次补偿的延迟时间乘数,默认值0表示忽略(eg:2, 第一次 delay * 2 第二次 delay * 2 * 2)

Spring-Retry
    /**
     * Synonym for {@link #delay()}.
     *
     * @return the delay in milliseconds (default 1000)
     */
    long value() default 1000;

    /**
     * A canonical backoff period. Used as an initial value in the exponential case, and
     * as a minimum value in the uniform case.
     * @return the initial or canonical backoff period in milliseconds (default 1000)
     */
    long delay() default 0;

    /**
     * The maximimum wait (in milliseconds) between retries. If less than the
     * {@link #delay()} then ignored.
     *
     * @return the maximum delay between retries (default 0 = ignored)
     */
    long maxDelay() default 0;

    /**
     * If positive, then used as a multiplier for generating the next delay for backoff.
     *
     * @return a multiplier to use to calculate the next backoff delay (default 0 =
     * ignored)
     */
    double multiplier() default 0;

    /**
     * An expression evaluating to the canonical backoff period. Used as an initial value
     * in the exponential case, and as a minimum value in the uniform case.
     * Overrides {@link #delay()}.
     * @return the initial or canonical backoff period in milliseconds.
     * @since 1.2
     */
    String delayExpression() default "";

    /**
     * An expression evaluating to the maximimum wait (in milliseconds) between retries.
     * If less than the {@link #delay()} then ignored.
     * Overrides {@link #maxDelay()}
     *
     * @return the maximum delay between retries (default 0 = ignored)
     * @since 1.2
     */
    String maxDelayExpression() default "";

    /**
     * Evaluates to a vaule used as a multiplier for generating the next delay for backoff.
     * Overrides {@link #multiplier()}.
     *
     * @return a multiplier expression to use to calculate the next backoff delay (default 0 =
     * ignored)
     * @since 1.2
     */
    String multiplierExpression() default "";

    /**
     * In the exponential case ({@link #multiplier()} &gt; 0) set this to true to have the
     * backoff delays randomized, so that the maximum delay is multiplier times the
     * previous delay and the distribution is uniform between the two values.
     *
     * @return the flag to signal randomization is required (default false)
     */
    boolean random() default false;
View Code

示例 

在主类上加上@EnableRetry注解,表示启用重试机制。

@EnableRetry
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

指定重试异常 和 重试指数 

    public Boolean testMethod(String arg1, Integer arg2) throws RuntimeException {
        logger.info("调用 testMethod 方法:arg1:" + arg1 + ",arg2:" + arg2);
        throw new RuntimeException("出错啦!");
    }

    @RequestMapping(value = "/testAn/{arg1}/{arg2}", method = { RequestMethod.GET })
    @Retryable(value = {Exception.class, RuntimeException.class}, backoff = @Backoff(multiplier = 1.5))
    public Boolean testExe(@PathVariable("arg1") String arg1, @PathVariable("arg2") Integer arg2) {
        Boolean result = testMethod(arg1, arg2);
        System.out.println("result : " + result);
        return result;
    }

    @Recover
    public Boolean backOffMethod(RuntimeException ex, String arg1, Integer arg2) {
        logger.info("调用 backOffMethod 方法:arg1:" + arg1 + ",arg2:" + arg2);
        return false;
    }

重试结果 

2019-08-07 15:24:31.790 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [testMethod:51] [eca27f812e304ec0964ddff868c1c98c] 调用 testMethod 方法:arg1:test-annotation,arg2:12
2019-08-07 15:24:32.791 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [testMethod:51] [eca27f812e304ec0964ddff868c1c98c] 调用 testMethod 方法:arg1:test-annotation,arg2:12
2019-08-07 15:24:34.291 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [testMethod:51] [eca27f812e304ec0964ddff868c1c98c] 调用 testMethod 方法:arg1:test-annotation,arg2:12
2019-08-07 15:24:34.292 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [backOffMethod:71] [eca27f812e304ec0964ddff868c1c98c] 调用 backOffMethod 方法:arg1:test-annotation,arg2:12

 

上一篇:前端总结


下一篇:三种方式在React中解决绑定this的作用域问题并传参