美文网首页程序员技术栈
spring retry, guava retrying 的整合

spring retry, guava retrying 的整合

作者: 老马啸西风2020 | 来源:发表于2019-06-20 22:55 被阅读2次

    Sisyphus

    支持过程式编程和注解编程的 java 重试框架。

    特性

    • 支持 fluent 过程式编程

    • 基于字节码的代理重试

    • 基于注解的重试,允许自定义注解

    • 无缝接入 spring

    • 接口与注解的统一

    • 解决 spring-retry 与 guava-retrying 中的不足之处

    设计目的

    综合了 spring-retry 和 gauva-retrying 的优势。

    调整一些特性,使其更利于实际使用。

    采用 Netty 类似的接口思想,保证接口的一致性,和替换的灵活性。

    借鉴 Hibernate-Validator 的设计,允许用户自定义注解。

    spring-retry 与 guava-retrying 中的不足之处

    更新记录

    更新记录

    开源地址

    sisyphus

    快速开始

    引入

    <plugin>
        <groupId>com.github.houbb</groupId>
        <artifactId>sisyphus-core</artifactId>
        <version>0.0.6</version>
    </plugin>
    

    入门代码

    详情参见 RetryerTest

    public void helloTest() {
        Retryer.<String>newInstance()
                .retry(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        System.out.println("called...");
                        throw new RuntimeException();
                    }
                });
    }
    

    代码分析

    • retry

    指定一个 callable 的实现。

    我们打印一条日志,并且模拟一个程序异常。

    日志信息

    日志信息

    called...
    called...
    called...
    

    和一些其他异常信息。

    重试触发的条件,默认是程序发生了异常

    这里的重试间隔默认为没有时间间隔,一共尝试3次。(包括第一次程序本身执行)

    为什么选择 sisyphus

    为什么选择 sisyphus

    作为开发者,我们一般都会选择比较著名的框架。

    比如 guava-retrying spring-retry。

    或者干脆自己写一个。

    为什么不是 guava-retrying/spring-retry

    java retry 这篇文章中我列举了常见的实现方式
    以及上述的两种框架,也讲述了其中的不足。

    guava-retrying 优缺点

    优点

    • 使用灵活

    • fluent 优雅写法

    • 提供足够多的实现

    缺点

    • 没有默认基于注解的实现

    • 重试策略设计并不友好

    spring-retry

    优点

    • 使用简单

    缺点

    • 重试条件单一

    • 重试等待策略单一

    • 无法自定义注解

    为什么不自己写一个

    个人感受

    我作为一名开发,平时说实在的,看到重试。

    我肯定会偷懒写一个 for 循环,重试几次就结束了。

    因为时间不允许。

    如果你更勤快一点,就可以选择 spring-retry/guava-retrying。如果你熟悉他们的优缺点的话。

    如果你渴望创造

    sisyphus 所有的实现都是基于接口的。

    你完全可以实现自己的实现,所有的东西基本完全可以被替换。

    当然一些常见的策略实现,项目的基本框架都有详尽的注释,当做参考也可以有一点帮助。

    sisyphus 做的更多的事情

    netty 的灵感

    参考了 netty 的设计,保证接口实现的一致性。

    而且 sisyphus 还做了更多,还保证了接口和注解之间的一致性。

    使用引导类,保证使用时的便利性,后期拓展的灵活性。

    hibernate-validator

    hibernate-validator 的作者是我知道为数不多的对于 java 注解应用很棒的开发者。(虽然所知甚少)

    自定义注解就是从这个框架中学来的。

    与 spring 为伍

    spring 基本与我们的代码形影不离,所以你可以很简单的结合 spring.

    就像你使用 spring-retry 一样。

    sisyphus 模块简介

    模块划分

    sisyphus 在模块划分的时候考虑到使用者的方便,主要有几个模块:

    sisyphus-api

    接口定义模块,是最基础的部分。

    会被 sisyphus-core 默认依赖。

    一般不需要引入,如果你想根据它实现自己的重试框架,不妨一试。

    sisyphus-core

    对于 sisyphus-api 模块的默认实现。

    并且添加易于使用的 Fluent 引导类,可以很方便的写出声明式的重试代码。

    sisyphus-annotation

    sisyphus 的注解实现模块。

    (1)基于字节码实现的代理重试,可以不依赖 spring。平时使用也更加灵活

    (2)允许自定义注解及其实现。使用者可以编写属于自己的重试注解。

    sisyphus-spring

    spring 做为 java 开发的引导者。自然是要支持的。

    你可以和使用 spring-retry 一样方便的使用 sisyphus-spring。

    模块间的依赖关系

    sisyphus-api
    sisyphus-core
    sisyphus-annotation
    sisyphus-spring
    sisyphus-test
    

    sisyphus-api 是基础的,灵活性最高。

    sisyphus-spring 是最简单易用的,灵活性相对较差。

    sisyphus-test 仅仅用作测试,不用外部引入。

    sisyphus 配置概览

    为了满足更加方便的配置,Retryer 类提供了许多可以配置的信息。

    默认配置

    /**
     * 默认配置测试
     */
    public void defaultConfigTest() {
        Retryer.<String>newInstance()
                .condition(RetryConditions.hasExceptionCause())
                .retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context())
                .maxAttempt(3)
                .listen(RetryListens.noListen())
                .recover(Recovers.noRecover())
                .callable(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        System.out.println("called...");
                        throw new RuntimeException();
                    }
                }).retryCall();
    }
    

    和下面的代码是等价的:

    public void helloTest() {
        Retryer.<String>newInstance()
                .callable(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        System.out.println("called...");
                        throw new RuntimeException();
                    }
                }).retryCall();
    }
    

    方法说明

    condition

    重试触发的条件,可以指定多个条件。

    默认为抛出异常。

    retryWaitContext

    重试等待的策略,可以指定多个。

    默认为不做任何等待。

    maxAttempt

    指定最大重试次数,包括第一次执行。

    默认值:3 次。

    listen

    指定重试的监听实现,默认为不做监听。

    recover

    当重试完成之后,依然满足重试条件,则可以指定恢复的策略。

    默认不做恢复。

    callable

    待重试执行的方法。

    retryCall

    触发重试执行。

    接口的详细介绍

    接口及其实现

    所有的接口,都可以直接查看对应的子类实例。

    用户自定义

    基于替换的灵活性,用户可以实现接口,定义更符合自己业务的实现。

    sisyphus 注解

    配置具有很高的灵活性,但是对于开发人员的使用,就没有注解那样简单灵活。

    所以本框架也实现了基于注解的重试。

    设计的规范

    保证接口和注解二者的统一性。

    maven 引入

    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>sisyphus-annotation</artifactId>
        <version>${project.version}</version>
    </dependency>
    

    注解

    核心注解主要有两个。

    Retry

    用于指定重试的相关配置。

    /**
     * 重试注解
     * 1. 实际需要,只允许放在方法上。
     * 2. 如果放在接口上,是否所有的子类都生效?为了简单明确,不提供这种实现。
     * 3. 保持注解和接口的一致性。{@link com.github.houbb.sisyphus.api.core.Retry} 接口
     * @author binbin.hou
     * @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 {};
    
    }
    

    RetryWait

    用于指定重试的等待策略。

    package com.github.houbb.sisyphus.annotation.annotation;
    
    import com.github.houbb.sisyphus.annotation.annotation.metadata.RetryWaitAble;
    import com.github.houbb.sisyphus.annotation.handler.impl.DefaultRetryWaitAbleHandler;
    import com.github.houbb.sisyphus.core.constant.RetryWaitConst;
    import com.github.houbb.sisyphus.core.support.wait.NoRetryWait;
    
    import java.lang.annotation.*;
    
    /**
     * 重试等待策略
     * 1. 为了对应重试策略,所有的内置注解应该实现当前的注解。
     * 2. 是否允许自定义注解?
     *
     * 当注解+对象同时出现的时候,视为组合。
     *
     * @author binbin.hou
     * @since 0.0.3
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    @Target(ElementType.ANNOTATION_TYPE)
    @RetryWaitAble(DefaultRetryWaitAbleHandler.class)
    public @interface RetryWait {
    
        /**
         * 默认值
         * 1. fixed 模式,则对应固定等待时间
         * 2. 递增
         * @return 默认值
         */
        long value() default RetryWaitConst.VALUE_MILLS;
    
        /**
         * 最小值
         * @return 最小值
         */
        long min() default RetryWaitConst.MIN_MILLS;
    
        /**
         * 最大值
         * @return 最大值
         */
        long max() default RetryWaitConst.MAX_MILLS;
    
        /**
         * 影响因数
         * 1. 递增重试,默认为 {@link RetryWaitConst#INCREASE_MILLS_FACTOR}
         * 2. 指数模式。默认为 {@link RetryWaitConst#MULTIPLY_FACTOR}
         * @return 影响因数
         */
        double factor() default Double.MIN_VALUE;
    
        /**
         * 指定重试的等待时间 class 信息
         * @return 重试等待时间 class
         */
        Class<? extends com.github.houbb.sisyphus.api.support.wait.RetryWait> retryWait() default NoRetryWait.class;
    
    }
    

    注解的使用

    定义好了注解,肯定要有注解的相关使用。

    关于注解的使用,主要有两种方式。

    Proxy+CGLIB

    基于代理模式和字节码增强。

    如果是项目中没有使用 spring,直接使用这种方式比较方便。

    Spring-AOP

    可以和 spring 直接整合。

    使用方式和 spring-retry 是一样的。

    sisyphus 代理模板

    目的

    为了便于用户更加方便地使用注解,同时又不依赖 spring。

    提供基于代码模式+字节码增强实现的方式。

    使用案例

    maven 引入

    引入注解相关模块。

    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>sisyphus-annotation</artifactId>
        <version>${project.version}</version>
    </dependency>
    

    定义测试方法

    以下测试代码可以参考 spring-test 模块。

    • MenuServiceImpl.java
    public class MenuServiceImpl {
    
        public void queryMenu(long id) {
            System.out.println("查询菜单...");
            throw new RuntimeException();
        }
    
        @Retry
        public void queryMenuRetry(long id) {
            System.out.println("查询菜单...");
            throw new RuntimeException();
        }
    
    }
    

    测试

    使用 RetryTemplate 进行测试

    无重试注解的方法

    @Test(expected = RuntimeException.class)
    public void templateTest() {
        MenuServiceImpl menuService = RetryTemplate.getProxyObject(new MenuServiceImpl());
        menuService.queryMenu(1);
    }
    
    • 日志信息
    查询菜单...
    

    只请求了一次。

    有注解的方法

    @Test(expected = RuntimeException.class)
    public void templateRetryTest() {
        MenuServiceImpl menuService = RetryTemplate.getProxyObject(new MenuServiceImpl());
        menuService.queryMenuRetry(1);
    }
    
    • 日志信息
    查询菜单...
    查询菜单...
    查询菜单...
    

    其他

    当然还有更多的配置,可以自行尝试。

    如果你想结合 spring 使用注解,请继续往下看。

    sisyphus spring 整合

    目的

    类似于 spring-retry 框架,如果你使用 spring 框架,那么整合本项目将会非常简单。

    注解的方式和过程式编程,二者尽可能的保持一致性,你想从一种方式变为另一种也比较简单。

    想从 spring-retry 切换到本框架也很方便。

    使用示例

    maven 引入

    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>sisyphus-spring</artifactId>
        <version>${project.version}</version>
    </dependency>
    

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

    业务代码

    你可以参考 sisyphus-test 模块。

    下面模拟非常常见的一些业务方法。

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

    • SpringService.java
    public interface SpringService {
    
        /**
         * 查询示例代码
         * @return 结果
         */
        String query();
    
    }
    
    • SpringServiceImpl.java
    import com.github.houbb.sisyphus.annotation.annotation.Retry;
    import com.github.houbb.sisyphus.test.service.SpringService;
    import org.springframework.stereotype.Service;
    
    /**
     * @author binbin.hou
     * @since 0.0.4
     */
    @Service
    public class SpringServiceImpl implements SpringService {
    
        @Override
        @Retry
        public String query() {
            System.out.println("spring service query...");
            throw new RuntimeException();
        }
    
    }
    

    开启重试

    基于注解直接如下配置即可。

    使用 @EnableRetry 标识需要开启重试。

    @Configurable
    @ComponentScan(basePackages = "com.github.houbb.sisyphus.test.service")
    @EnableRetry
    public class SpringConfig {
    }
    

    测试代码

    import com.github.houbb.sisyphus.test.config.SpringConfig;
    import com.github.houbb.sisyphus.test.service.SpringService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    /**
     * @author binbin.hou
     * @since 0.0.4
     */
    @ContextConfiguration(classes = SpringConfig.class)
    @RunWith(SpringJUnit4ClassRunner.class)
    public class SpringServiceTest {
    
        @Autowired
        private SpringService springService;
    
        @Test(expected = RuntimeException.class)
        public void queryTest() {
            springService.query();
        }
    
    }
    
    • 日志信息
    spring service query...
    spring service query...
    spring service query...
    

    新特性预定

    入参

    重试上下文添加入参信息

    配置优化

    提供更加优异的配置体验

    相关文章

      网友评论

        本文标题:spring retry, guava retrying 的整合

        本文链接:https://www.haomeiwen.com/subject/pvisqctx.html