1、介绍

日常开发中经常需要调用第三方接口,有些接口需要在特定异常下进行重试,为了避免一直在调用接口,每次调用直接需要间隔一段时间,并且需要设置个上限,达到最大重试次数后抛出异常;对该异常进行一致性处理,按一致性补偿处理或者记录异常并推送提醒。
常用的做法是写个循环,不断调用接口,并设置睡眠时间;手动写重试方法需要考虑的异常问题较多,这里介绍个spring自带的retry,使用简单,即插即用。@Retryable也是通过AOP方式实现,因此重试的方法不能在同一类中调用。

2、 Retryable使用

在启动类中添加@EnableRetry注解

@EnableRetry
@ServletComponentScan
@SpringBootApplication
public class WebApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }
    
    /**
     * 支持打成war包部署
     *
     * @date
     */
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WebApplication.class);
    }

}

业务service类中添加重试方法,通过@Retryable注解设置重试策略

int count = 0;
@Retryable(include = {RuntimeException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 2))
    public void retryMethod() {
        System.out.println("执行重试count=" + count++ + " 执行时间" + LocalDateTime.now());
        /**
         * 调用第三方接口 异常
         */
        //抛出异常
        throw new RuntimeException();
    }

执行结果

执行重试count=0 执行时间2021-01-05T17:18:18.124
执行重试count=1 执行时间2021-01-05T17:18:20.124
执行重试count=2 执行时间2021-01-05T17:18:24.126

常用的参数

  1. value/include 处理的特定异常
  2. maxAttempts 最大重试次数,默认3次
  3. backoff 指定的重试策略,delay为第一次延迟时间(毫秒),multiplier为倍数

Retryable 注解中的各参数如下

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

	/**
	 * Retry interceptor bean name to be applied for retryable method. Is mutually
	 * exclusive with other attributes.
	 * @return the retry interceptor bean name
	 */
	String interceptor() default "";

	/**
	 * 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).
	 * If includes is empty but excludes is not, all not excluded exceptions are retried
	 * @return exception types not to retry
	 */
	Class<? extends Throwable>[] exclude() default {};

	/**
	 * A unique label for statistics reporting. If not provided the caller may choose to
	 * ignore it, or provide a default.
	 *
	 * @return the label for the statistics
	 */
	String label() default "";

	/**
	 * Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the
	 * retry policy is applied with the same policy to subsequent invocations with the
	 * same arguments. If false then retryable exceptions are not re-thrown.
	 * @return true if retry is stateful, default false
	 */
	boolean stateful() default false;

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

	/**
	 * @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3
	 * Overrides {@link #maxAttempts()}.
	 * @since 1.2
	 */
	String maxAttemptsExpression() default "";

	/**
	 * Specify the backoff properties for retrying this operation. The default is a
	 * simple {@link Backoff} specification with no properties - see it's documentation
	 * for defaults.
	 * @return a backoff specification
	 */
	Backoff backoff() default @Backoff();

	/**
	 * Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
	 * returns true - can be used to conditionally suppress the retry. Only invoked after
	 * an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
	 * Other beans in the context can be referenced.
	 * For example:
	 * <pre class=code>
	 *  {@code "message.contains('you can retry this')"}.
	 * </pre>
	 * and
	 * <pre class=code>
	 *  {@code "@someBean.shouldRetry(#root)"}.
	 * </pre>
	 * @return the expression.
	 * @since 1.2
	 */
	String exceptionExpression() default "";

	/**
	 * Bean names of retry listeners to use instead of default ones defined in Spring context
	 * @return retry listeners bean names
	 */
	String[] listeners() default {};

}

若要在达到最大次数后,处理特定异常,进行一致性任务处理,事务回滚或者推送提醒等,可通过@Recover处理,达到执行最大重试次数,会执行该方法。其中recover方法中的入参为重试中抛出的指定异常,且重试方法返回参数必须为void。

@Recover
    public void recover(RuntimeException e) {
        log.error("recover method", e);
        System.out.println("一致性处理补偿 : ");
    }

3、 自定义策略

若需要控制第三方接口的超时时间,以及需要返回重试接口的错误异常信息,可以通过自定义策略模块,重写dowithRetry方法实现,自定义重试策略的超时时间。

public void retryPolicy() {
        RetryTemplate template = new RetryTemplate();

        TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
        policy.setTimeout(6000L);

        template.setRetryPolicy(policy);

        String result = template.execute(new RetryCallback<String, RuntimeException>() {
            @Override
            public String doWithRetry(RetryContext context) {
                // Do stuff that might fail, e.g. webservice operation
                return "";
            }

        });
    }

其中doWithRetry方法的返回参数可按需要返回所要的类型,TimeoutRetryPolicy为超时的策略继承了RetryPolicy接口。

4、总结

spring自带的重试机制,使用方便,简单,可指定重试异常和重试间隔时间等;并且提供自定义重试模板方法,自定义重试策略,可对接口调用超时异常进行处理重试。spring自带的重试方法在spring家族使用广泛,spring batch和spring单例等方法中都有使用。