• 接口重试实现


    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.

  • 相关阅读:
    猴子分香蕉
    打鱼晒网
    质数/素数
    三角形-->九九乘法表
    eclipse 导入gradle引入多模块项目,引入eclipse后变成了好几个工程
    linux 命令基础大全
    SQL Server 增加链接服务器
    Postgresql数据库部署之:Postgresql 存在session 会话不能删除数据库
    Postgresql数据库部署之:Postgresql本机启动和Postgresql注册成windows 服务
    Git常用命令使用大全
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/13686248.html
Copyright © 2020-2023  润新知