接口重试实现

sisyphus

github:https://github.com/houbb/sisyphus

一、工具类方式使用

1、POM

        <!-- retry -->
            <dependency>
                <groupId>com.github.houbb</groupId>
                <artifactId>sisyphus-core</artifactId>
                <version>0.0.8</version>
            </dependency>
            <dependency>
                <groupId>com.github.houbb</groupId>
                <artifactId>sisyphus-annotation</artifactId>
                <version>0.0.8</version>
            </dependency>

2、SisyphusUtil:

package com.xxx.mitv.common.util;

import com.github.houbb.sisyphus.core.core.RetryWaiter;
import com.github.houbb.sisyphus.core.core.Retryer;
import com.github.houbb.sisyphus.core.support.condition.RetryConditions;
import com.github.houbb.sisyphus.core.support.listen.RetryListens;
import com.github.houbb.sisyphus.core.support.recover.Recovers;
import com.github.houbb.sisyphus.core.support.wait.ExponentialRetryWait;
import com.github.houbb.sisyphus.core.support.wait.NoRetryWait;

import java.util.concurrent.Callable;

/**
 * 重试工具类
*/ public class SisyphusUtil { /** * 默认配置 * * @param callable 待重试执行方法 */ public static void defaultAttempt(final Callable<String> callable) { Retryer.<String>newInstance() //重试触发的条件,可以指定多个条件,默认为抛出异常。 .condition(RetryConditions.hasExceptionCause()) //重试等待的策略,可以指定多个,默认为不做任何等待。 .retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context()) //指定最大重试次数,包括第一次执行,默认3次 .maxAttempt(3) //指定重试的监听实现,默认为不做监听。 .listen(RetryListens.noListen()) //当重试完成之后,依然满足重试条件,则可以指定恢复的策略。默认不做恢复。 .recover(Recovers.noRecover()) //待重试执行的方法。 .callable(callable) //触发重试执行。 .retryCall(); } /** * 根据返回值等于期望值定制重试规则 * * @param condition 触发条件 * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForEqualsResult(final String condition, final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.isEqualsResult(condition)) //递增策略 .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } /** * 根据返回值不等于期望值定制重试规则 * * @param condition 触发条件 * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForNotEqualsResult(final String condition, final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.isNotEqualsResult(condition)) //递增策略 .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } /** * 根据是否存在异常定制重试规则 * * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForException(final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.hasExceptionCause()) .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } }

3、应用

  工具类方式:

  //重试3次
    SisyphusUtil.attemptForNotEqualsResult(
         "true",
         3,
         60000,
         2.0,
         new Callable<String>() {
        @Override
        public String call() throws Exception {
            return autoRenewService.wechatAdvanceNotify(info) == true ? "true" : "false";
        }
    });

   注解方式:与Spring整合

  @Retry :

/**
 * 重试注解
 * 1. 实际需要,只允许放在方法上。
 * 2. 如果放在接口上,是否所有的子类都生效?为了简单明确,不提供这种实现。
 * 3. 保持注解和接口的一致性。{@link com.github.houbb.sisyphus.api.core.Retry} 接口
 * @since 0.0.3
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RetryAble(DefaultRetryAbleHandler.class)
public @interface Retry {

    /**
     * 重试类实现
     * @return 重试
     * @since 0.0.5
     */
    Class<? extends com.github.houbb.sisyphus.api.core.Retry> retry() default DefaultRetry.class;

    /**
     * 最大尝试次数
     * 1. 包含方法第一次正常执行的次数
     * @return 次数
     */
    int maxAttempt() default 3;

    /**
     * 重试触发的场景
     * @return 重试触发的场景
     */
    Class<? extends RetryCondition> condition() default ExceptionCauseRetryCondition.class;

    /**
     * 监听器
     * 1. 默认不进行监听
     * @return 监听器
     */
    Class<? extends RetryListen> listen() default NoRetryListen.class;

    /**
     * 恢复操作
     * 1. 默认不进行任何恢复操作
     * @return 恢复操作对应的类
     */
    Class<? extends Recover> recover() default NoRecover.class;

    /**
     * 等待策略
     * 1. 支持指定多个,如果不指定,则不进行任何等待,
     * @return 等待策略
     */
    RetryWait[] waits() default {};

}

二、与Spring整合:

  1、POM

            <dependency>
                <groupId>com.github.houbb</groupId>
                <artifactId>sisyphus-spring</artifactId>
                <version>0.0.8</version>
            </dependency>    

  会默认引入 spring 以及 AOP 相关 jar

  2、开启重试

  @EnableRetry

/**
 * 启用重试注解
 * @since 0.0.4
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RetryAopConfig.class)
@EnableAspectJAutoProxy
public @interface EnableRetry {
}

  3、使用 @Retry 标识方法需要进行重试。

  如:

    @Retry
    @Override
    public void test() {
        LOGGER.info("test");
        int i=1/0;
    }

spring retry

https://github.com/spring-projects/spring-retry#javaConfigForRetryProxies

翻译的中文文档:https://segmentfault.com/a/1190000019932970

总结好文:https://albenw.github.io/posts/69a9647f/

spring retry是通过 AOP 实现的

1、依赖

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

如果使用 @Retryable 注解还需额外添加 aop和aspectj的依赖

   <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
    </dependency>

2、在启动类或配置类加上 @EnableRetry 注解,表示启用重试机制

3、举例:

@Configuration
@EnableRetry
public class Application {

    @Bean
    public Service service() {
        return new Service();
    }

}

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}

在此例中,当调用service()方法出现 RemoteAccessException 异常时,默认重试 3次(包含第一次的失败),如果仍失败,则会调用 recover 方法。

@Retryable 注解属性中有各种选项,用于包含和排除异常类型、限制重试次数和回退策略。

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

    /**
     * 为重试方法应用重试拦截器的bean名称。与其他属性互斥
     */
    String interceptor() default "";

    /**
     * 可以重试的异常类型。与includes属性同义。默认值为空(并且如果exclude也是空的话,所有的异常都会重试)
     */
    Class<? extends Throwable>[] value() default {};

    Class<? extends Throwable>[] include() default {};

    /**
     * 不重试的异常类型,默认为空(并且如果exclude也是空的话,所有的异常都会重试)
     */
    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;

    /**
     * 尝试的最大次数(包含第一次失败),默认为3
     */
    int maxAttempts() default 3;

    /**
     * 返回一个求尝试最大次数值的表达式(包含第一次失败),默认为3
     * 重写 {@link #maxAttempts()}。
     */
    String maxAttemptsExpression() default "";

    /**
     * 退避策略,指怎么去做下一次的重试,在这里其实就是两次重试之间的间隔
     */
    Backoff backoff() default @Backoff();

    /**
     * 在SimpleRetryPolicy.canRetry()返回true之后执行一个计算表达式,可用来有条件的取消重试
     * 只在抛出异常后调用,求值的root对象为上一次的异常,可以引用上下文中的其他beans
     */
    String exceptionExpression() default "";
}

@Backoff

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Backoff {

    /**
     * 与 delay() 属性同义
     * 返回延迟多少毫秒后重试(默认为1000毫秒)
     */
    long value() default 1000;

    /**
     * 一个标准的再重试周期,默认1000毫秒
     * @return the initial or canonical backoff period in milliseconds (default 1000)
     */
    long delay() default 0;

    /**
     * 重试之间最大等待(毫秒)时间。如果小于 {@link #delay()} 则忽略。
     * @return the maximum delay between retries (default 0 = ignored)
     */
    long maxDelay() default 0;

    /**
     * 如果是正数,则用于生成下次再重试等待时间的乘数
     * 返回一个乘数用于计算下次再重试延迟(默认为0忽略)
     */
    double multiplier() default 0;

    /**
     * 标准再重试周期求值表达式。在指数情况下用作初始值,始终如一的情况下用作最小值。
     * Overrides {@link #delay()}.
     * @return the initial or canonical backoff period in milliseconds.
     * @since 1.2
     */
    String delayExpression() default "";

    /**
     * 在重试之间最大等待(毫秒)数的求值表达式。
     *  * 如果小于 {@link #delay()} 则忽略。
     * Overrides {@link #maxDelay()}
     * @return the maximum delay between retries (default 0 = ignored)
     * @since 1.2
     */
    String maxDelayExpression() default "";

    /**
     * 表达式求值作为生成下次再重试延迟的乘数
     * Overrides {@link #multiplier()}.
     * @since 1.2
     */
    String multiplierExpression() default "";

    /**
     * 在指数情况下 ({@link #multiplier()} > 0) 设置该值为true将使再重试延迟随机化,
     * 使最大延迟为先前延迟的乘数倍数,并使这两个延迟值之间分布均匀。
     * 默认为false
     */
    boolean random() default false;
}

实例:

    /**
     * 第一次延迟1000ms,第二次延迟2*1000ms,第三次延迟2*2*1000ms,之后都是延迟5000ms
     */
    @Retryable(maxAttempts = 6, backoff = @Backoff(delay = 1000, maxDelay = 5000, multiplier = 2))
    @Override
    public void test() {
        LOGGER.error("spring retry:{}", DateUtil.formatDate(new Date()));
        throw new RuntimeException("my test");
    }

输出:

2020-09-28 14:12:24.536|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=0
2020-09-28 14:12:24.539|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:24 GMT
2020-09-28 14:12:24.539|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 1000
2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=1
2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=1 2020-09-28 14:12:25.541|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:25 GMT 2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 2000 2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=2
2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=2 2020-09-28 14:12:27.542|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:27 GMT 2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 40002020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=3
2020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=3 2020-09-28 14:12:31.542|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:31 GMT 2020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 50002020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=4
2020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=4 2020-09-28 14:12:36.543|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:36 GMT 2020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 50002020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=5
2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=5 2020-09-28 14:12:41.544|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:41 GMT 2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=6 2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:346|Retry failed last attempt: count=6 2020-09-28 14:12:41.546|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|LogAspect#around:59|test方法执行异常:my test java.lang.RuntimeException: my test

Spring Retry缺点

  1、其回退策略,默认使用的是Thread.sleep方法,会导致当前的线程被阻塞,因此使用的时候要注意。

  2、只能在异常后重试,重试条件单一

END.

原文地址:https://www.cnblogs.com/yangyongjie/p/13686248.html