重试组件 Spring Retry

spring-retry是spring社区的一个成员,它提供了一种对失败操作进行自动重试的能力,可以作为某些瞬时错误(例如短暂的网络抖动)的解决方案。

作为spring生态的一部分,spring-retry自然地支持声明式(Declarative)方式使用。此外,它也支持命令式(Impertive)方式在代码里直接调用。

1. 项目集成

引入依赖:

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

它的版本交给spring boot管理,以获得与spring良好的兼容性。

项目当前使用的是spring-boot版本为1.5.13.RELEASE,它管理的spring-retry版本是1.2.2.RELEASE。

2. 声明式使用方式

  1. 在spring boot启动类上增加@EnableRetry注解:

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

    该注解的proxyTargetClass属性默认为false,表示使用JDK的动态代理。如果设置为true,则表示使用CGLIB作为动态代理。

  2. 在需要重试的方法上,增加@Retryable注解:

    @Service
    @Slf4j
    public class RetryAnnotationTest {
    
        @Retryable(value = {RemoteAccessException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000L), recover = "recoverCall")
        public boolean call(String param){
            System.out.println(new Date());
            return RetryTask.retryTask(param);
        }
    
        @Recover
        public boolean recoverCall(Exception e,String param) {
            log.error("达到最大重试次数,或抛出了一个没有指定进行重试的异常:", e);
            return false;
     }
    }
    

    如上示例中,当call方法抛出RemoteAccessException异常时,spring retry会重新调用call方法,重试次数为5次,两次重试之间间隔为1s。

    如果超过最大重试次数仍未成功,或者抛出非RemoteAccessException异常,则调用recoverCall方法。

    注:@Retryable注解也可以作用在类上,作用在类上之后,spring retry会对类的全部方法进行重试。

3. 命令式使用方式

  1. 配置RetryTemplate:

    @Configuration
    public class SpringRetryConfig {
       
        @Bean("retryTemplateFixed")
        public RetryTemplate retryTemplateFixed() {
            // 1.重试策略
            // 触发条件
            Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
            exceptionMap.put(RemoteAccessException.class, true);
            
            // 重试次数设置为3次
            int maxAttempts = 3;
            SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxAttempts, exceptionMap);
    
            // 2.重试间隔设置为1秒
            FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
            backOffPolicy.setBackOffPeriod(1000);
    
            // 3.构造RetryTemplate
            RetryTemplate retryTemplate = new RetryTemplate();
            retryTemplate.setRetryPolicy(retryPolicy);
            retryTemplate.setBackOffPolicy(backOffPolicy);
            return retryTemplate;
        }
        
        @Bean("retryTemplate")
        public RetryTemplate retryTemplate() {
            // 定义简易重试策略,最大重试次数为3次,重试间隔为3s
            return RetryTemplate.builder()
                    .maxAttempts(3)
                    .fixedBackoff(3000)
                    .retryOn(RuntimeException.class)
                    .build();
        }
        
    }
    

    注:可以配置多个RetryTemplate,用以适应不同重试场景。

    spring retry支持的重试策略和退避策略如下:

    @Bean("retryTemplateDemo")
    public RetryTemplate retryTemplateDemo() {
        // 1.重试策略
        // 不重试
        NeverRetryPolicy neverRetryPolicy = new NeverRetryPolicy();
    
        // 无限重试
        AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy();
    
        // 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
        ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy = new ExceptionClassifierRetryPolicy();
        final Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>(3);
        policyMap.put(IOException.class, alwaysRetryPolicy);
        policyMap.put(InterruptedIOException.class, neverRetryPolicy);
        policyMap.put(UnknownHostException.class, neverRetryPolicy);
        exceptionClassifierRetryPolicy.setPolicyMap(policyMap);
    
        // 固定次数重试,默认最大重试次数为5次,RetryTemplate默认重试策略
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(5);
    
        // 超时时间重试,默认超时时间为1秒,在指定的超时时间内重试
        TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
        timeoutRetryPolicy.setTimeout(3000);
    
        /*
         * 组合重试策略,有两种组合方式:
         *  1.悲观默认重试,有不重试的策略则不重试。
         *  2.乐观默认不重试,有需要重试的策略则重试。
         */
        CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
        compositeRetryPolicy.setOptimistic(true);
        compositeRetryPolicy.setPolicies(new RetryPolicy[]{simpleRetryPolicy, timeoutRetryPolicy});
    
        // 有熔断功能的重试
        CircuitBreakerRetryPolicy circuitBreakerRetryPolicy = new CircuitBreakerRetryPolicy(compositeRetryPolicy);
        // 5s内失败10次,则开启熔断
        circuitBreakerRetryPolicy.setOpenTimeout(5000);
        // 10s之后熔断恢复
        circuitBreakerRetryPolicy.setResetTimeout(10000);
    
        // 2.退避策略(上一次执行失败之后,间隔多久进行下一次重试)
        // 立即重试
        NoBackOffPolicy noBackOffPolicy = new NoBackOffPolicy();
    
        // 固定时间后重试,默认1s
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(1000);
    
        // 随机时间后重试(如下:从500ms到1500ms内取一个随机时间后进行重试)
        UniformRandomBackOffPolicy uniformRandomBackOffPolicy = new UniformRandomBackOffPolicy();
        uniformRandomBackOffPolicy.setMinBackOffPeriod(500);
        uniformRandomBackOffPolicy.setMaxBackOffPeriod(1500);
    
        // 指数退避策略(如下:初始休眠时间100ms,最大休眠时间30s,下一次休眠时间为当前休眠时间*2)
        ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
        exponentialBackOffPolicy.setInitialInterval(100);
        exponentialBackOffPolicy.setMaxInterval(30000);
        exponentialBackOffPolicy.setMultiplier(2);
    
        // 随机指数退避策略
        ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
        exponentialRandomBackOffPolicy.setInitialInterval(100);
        exponentialRandomBackOffPolicy.setMaxInterval(30000);
        exponentialRandomBackOffPolicy.setMultiplier(2);
    
        // 3.return
        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setRetryPolicy(circuitBreakerRetryPolicy);
        return retryTemplate;
    }
    
  2. 在代码中调用

    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class RetryTest {
    
        // 注入RetryTemplate
        @Resource
        private RetryTemplate retryTemplateFixed;
    
        @Test
        public void test() {
            // 执行
            Boolean execute = retryTemplateFixed.execute(
                    // 重试回调
                    retryContext -> {
                        System.out.println(new Date());
                        boolean b = RetryTask.retryTask("abc");
                        log.info("调用的结果:{}", b);
                        return b;
                    },
                    // 恢复回调(达到最大重试次数,或者抛出不满足重试条件的异常)
                    retryContext -> {
                        log.info("已达到最大重试次数或抛出了不重试的异常~~~");
                        return false;
                    }
            );
    
            log.info("执行结果:{}",execute);
    
        }
    
    }
    
  3. 监听重试过程

    通过实现RetryListener接口,重写open、close、onError这三个方法,既可以完成对重试过程的追踪,也可以添加额外的处理逻辑。

    @Slf4j
    @Component
    public class RetryListenerTemplate implements RetryListener {
        // 进入重试前调用
        @Override
        public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) {
            log.info("--------------------------进入重试方法--------------------------");
        return true;
        }
    
        // 重试结束后调用
        @Override
        public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
        log.info("--------------------------重试方法结束--------------------------");
        }
    
        // 捕获到异常时调用
        @Override
        public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
            log.info("--------------------------第" + retryContext.getRetryCount() + "次重试--------------------------");
            log.error(throwable.getMessage(), throwable);
    }
    }
    

    或者,通过继承RetryListenerSupport,也可以从open、close、onError这三个方法中,选择性的重写。

    public class RetryListener4Open extends RetryListenerSupport {
        @Override
        public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
            return super.open(context, callback);
        }
    }
    

    在实例化RetryTemplate时,配置上该RetryListener实例即可。

    retryTemplate.setListeners(new RetryListener[] {retryListenerTemplate});
    

    注:

    1. V2.0版本以后,新增一个doOnSuccess方法,可以在调用成功之后,根据返回的结果值,来决定是否要进行重试。
    2. 每个RetryTemplate可以注册多个监听器,其中onOpen、onClose方法按照注册顺序执行,onError按照注册顺序的相反顺序执行。

参考资料:

  1. Spring-Retry 和 Guava-Retry
  2. Spring Retry Github地址
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值