• Spring Retry 重试


    重试的使用场景比较多,比如调用远程服务时,由于网络或者服务端响应慢导致调用超时,此时可以多重试几次。用定时任务也可以实现重试的效果,但比较麻烦,用Spring Retry的话一个注解搞定所有。话不多说,先看演示。

    首先引入依赖

    <dependency>
        <groupId>org.springframework.retrygroupId>
        <artifactId>spring-retryartifactId>
        <version>1.3.4version>
    dependency>
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.9.9.1version>
    dependency>

    使用方式有两种:命令式和声明式

    命令式

    /**
     * 命令式的方式使用Spring Retry
     */
    @GetMapping("/hello")
    public String hello(@RequestParam("code") Integer code) throws Throwable {
        RetryTemplate retryTemplate = RetryTemplate.builder()
                .maxAttempts(3)
                .fixedBackoff(1000)
                .retryOn(RemoteAccessException.class)
                .build();
        retryTemplate.registerListener(new MyRetryListener());
    
        String resp = retryTemplate.execute(new RetryCallback() {
            @Override
            public String doWithRetry(RetryContext context) throws Throwable {
                return helloService.hello(code);
            }
        });
    
        return resp;
    }

    定义一个RetryTemplate,然后调用execute方法,可配置项比较多,不一一列举

    真正使用的时候RetryTemplate可以定义成一个Bean,例如:

    @Configuration
    public class RetryConfig {
        @Bean
        public RetryTemplate retryTemplate() {
            RetryTemplate retryTemplate = RetryTemplate.builder()
                    .maxAttempts(3)
                    .fixedBackoff(1000)
                    .withListener(new MyRetryListener())
                    .retryOn(RemoteAccessException.class)
                    .build();
            return retryTemplate;
        }
    }

    业务代码:

    /**
     * 命令式的方式使用Spring Retry
     */
    @Override
    public String hello(int code) {
        if (0 == code) {
            System.out.println("出错了");
            throw new RemoteAccessException("出错了");
        }
        System.out.println("处理完成");
        return "ok";
    }

    监听器实现:

    package com.example.retry.listener;
    
    import org.springframework.retry.RetryCallback;
    import org.springframework.retry.RetryContext;
    import org.springframework.retry.RetryListener;
    
    public class MyRetryListener implements RetryListener {
        @Override
        public extends Throwable> boolean open(RetryContext context, RetryCallback callback) {
            System.out.println("open");
            return true;
        }
    
        @Override
        public extends Throwable> void close(RetryContext context, RetryCallback callback, Throwable throwable) {
            System.out.println("close");
        }
    
        @Override
        public extends Throwable> void onError(RetryContext context, RetryCallback callback, Throwable throwable) {
            System.out.println("error");
        }
    }

    声明式(注解方式)

    /**
     * 注解的方式使用Spring Retry
     */
    @Retryable(value = Exception.class, maxAttempts = 2, backoff = @Backoff(value = 1000, delay = 2000, multiplier = 0.5))
    @Override
    public String hi(int code) {
        System.out.println("方法被调用");
        int a = 1/code;
        return "ok";
    }
    
    @Recover
    public String hiRecover(Exception ex, int code) {
        System.out.println("重试结束");
        return "asdf";
    }

    这里需要主要的几点

    1. @EnableRetry(proxyTargetClass = true/false)
    2. @Retryable 修饰的方法必须是public的,而且不能是同一个类中调用
    3. @Recover 修饰的方法签名必须与@Retryable修饰的方法一样,除了第一个参数外
    /**
     * 注解的方式使用Spring Retry
     */
    @GetMapping("/hi")
    public String hi(@RequestParam("code") Integer code) {
        return helloService.hi(code);
    }

    1. 用法

    声明式

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

    命令式

    RetryTemplate template = RetryTemplate.builder()
    				.maxAttempts(3)
    				.fixedBackoff(1000)
    				.retryOn(RemoteAccessException.class)
    				.build();
    
    template.execute(ctx -> {
        // ... do something
    });

    2. RetryTemplate

    为了自动重试,Spring Retry 提供了 RetryOperations 重试操作策略

    public interface RetryOperations {
    
         T execute(RetryCallback retryCallback) throws Exception;
    
         T execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback)
            throws Exception;
    
         T execute(RetryCallback retryCallback, RetryState retryState)
            throws Exception, ExhaustedRetryException;
    
         T execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback,
            RetryState retryState) throws Exception;
    
    }

    基本回调是一个简单的接口,允许插入一些要重试的业务逻辑:

    public interface RetryCallback {
    
        T doWithRetry(RetryContext context) throws Throwable;
    
    }

    回调函数被尝试,如果失败(通过抛出异常),它将被重试,直到成功或实现决定中止。

    RetryOperations最简单的通用实现是RetryTemplate

    RetryTemplate template = new RetryTemplate();
    
    TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
    policy.setTimeout(30000L);
    
    template.setRetryPolicy(policy);
    
    Foo result = template.execute(new RetryCallback() {
    
        public Foo doWithRetry(RetryContext context) {
            // Do stuff that might fail, e.g. webservice operation
            return result;
        }
    
    });

    从Spring Retry 1.3开始,RetryTemplate支持流式配置:

    RetryTemplate.builder()
          .maxAttempts(10)
          .exponentialBackoff(100, 2, 10000)
          .retryOn(IOException.class)
          .traversingCauses()
          .build();
    
    RetryTemplate.builder()
          .fixedBackoff(10)
          .withinMillis(3000)
          .build();
    
    RetryTemplate.builder()
          .infiniteRetry()
          .retryOn(IOException.class)
          .uniformRandomBackoff(1000, 3000)
          .build();

    3. RecoveryCallback

    当重试耗尽时,RetryOperations可以将控制传递给不同的回调:RecoveryCallback。

    Foo foo = template.execute(new RetryCallback() {
        public Foo doWithRetry(RetryContext context) {
            // business logic here
        },
      new RecoveryCallback() {
        Foo recover(RetryContext context) throws Exception {
              // recover logic here
        }
    });

    4. Listeners

    public interface RetryListener {
    
        void open(RetryContext context, RetryCallback callback);
    
        void onSuccess(RetryContext context, T result);
    
        void onError(RetryContext context, RetryCallback callback, Throwable e);
    
        void close(RetryContext context, RetryCallback callback, Throwable e);
    
    }

    在最简单的情况下,open和close回调在整个重试之前和之后,onSuccess和onError应用于个别的RetryCallback调用,onSuccess方法在成功调用回调之后被调用。

    5. 声明式重试

    有时,你希望在每次业务处理发生时都重试一些业务处理。这方面的典型例子是远程服务调用。Spring Retry提供了一个AOP拦截器,它将方法调用封装在RetryOperations实例中。RetryOperationsInterceptor执行被拦截的方法,并根据提供的RepeatTemplate中的RetryPolicy在失败时重试。

    你可以在 @Configuration 类上添加一个 @EnableRetry 注解,并且在你想要进行重试的方法(或者类)上添加 @Retryable 注解,还可以指定任意数量的重试监听器。

    @Configuration
    @EnableRetry
    public class Application {
    
        @Bean
        public Service service() {
            return new Service();
        }
    
        @Bean public RetryListener retryListener1() {
            return new RetryListener() {...}
        }
    
        @Bean public RetryListener retryListener2() {
            return new RetryListener() {...}
        }
    
    }
    
    @Service
    class Service {
        @Retryable(RemoteAccessException.class)
        public service() {
            // ... do something
        }
    }

    可以使用 @Retryable 的属性类控制 RetryPolicy 和 BackoffPolicy

    @Service
    class Service {
        @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
        public service() {
            // ... do something
        }
    }

    如果希望在重试用尽时采用替代代码返回,则可以提供恢复方法。方法应该声明在与@Retryable实例相同的类中,并标记为@Recover。返回类型必须匹配@Retryable方法。恢复方法的参数可以包括抛出的异常和(可选地)传递给原始可重试方法的参数(或者它们的部分列表,只要在需要的最后一个之前不省略任何参数)。

    @Service
    class Service {
        @Retryable(RemoteAccessException.class)
        public void service(String str1, String str2) {
            // ... do something
        }
        @Recover
        public void recover(RemoteAccessException e, String str1, String str2) {
           // ... error handling making use of original args if required
        }
    }

    若要解决可选择用于恢复的多个方法之间的冲突,可以显式指定恢复方法名称。

    @Service
    class Service {
        @Retryable(recover = "service1Recover", value = RemoteAccessException.class)
        public void service1(String str1, String str2) {
            // ... do something
        }
    
        @Retryable(recover = "service2Recover", value = RemoteAccessException.class)
        public void service2(String str1, String str2) {
            // ... do something
        }
    
        @Recover
        public void service1Recover(RemoteAccessException e, String str1, String str2) {
            // ... error handling making use of original args if required
        }
    
        @Recover
        public void service2Recover(RemoteAccessException e, String str1, String str2) {
            // ... error handling making use of original args if required
        }
    }

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



    欢迎各位转载,但必须在文章页面中给出作者和原文链接!
  • 相关阅读:
    你猜,怎么用一句话证明你是项目经理?
    密码技术---分组密码的模式
    损失函数总结(六):KLDivLoss、BCEWithLogitsLoss
    信息学奥赛一本通2062:【例1.3】电影票
    Win10 搭建FTP服务器
    长短期记忆神经网络(LSTM)的回归预测(免费完整源代码)【MATLAB】
    学习笔记:C++ 11新特性
    避免告警疲劳:每个 K8s 工程团队的 8 个技巧
    jmeter使用bzm线程组压测(Custom Thread Groups插件)
    OpenCV自学笔记二十四:支持向量机
  • 原文地址:https://www.cnblogs.com/cjsblog/p/16826063.html