Spring Retry 重试

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

首先引入依赖

<dependency>     <groupId>org.springframework.retry</groupId>     <artifactId>spring-retry</artifactId>     <version>1.3.4</version> </dependency> <dependency>     <groupId>org.aspectj</groupId>     <artifactId>aspectjweaver</artifactId>     <version>1.9.9.1</version> </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<String, Throwable>() {         @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"; }

Spring Retry 重试

Spring Retry 重试

Spring Retry 重试

监听器实现:

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 <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {         System.out.println("open");         return true;     }      @Override     public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {         System.out.println("close");     }      @Override     public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> 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); }

Spring Retry 重试

Spring Retry 重试

Spring Retry 重试

Spring Retry 重试

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> T execute(RetryCallback<T> retryCallback) throws Exception;      <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)         throws Exception;      <T> T execute(RetryCallback<T> retryCallback, RetryState retryState)         throws Exception, ExhaustedRetryException;      <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,         RetryState retryState) throws Exception;  }

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

public interface RetryCallback<T> {      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<Foo>() {      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<Foo>() {     public Foo doWithRetry(RetryContext context) {         // business logic here     },   new RecoveryCallback<Foo>() {     Foo recover(RetryContext context) throws Exception {           // recover logic here     } });

4. Listeners

public interface RetryListener {      void open(RetryContext context, RetryCallback<T> callback);      void onSuccess(RetryContext context, T result);      void onError(RetryContext context, RetryCallback<T> callback, Throwable e);      void close(RetryContext context, RetryCallback<T> 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

发表评论

评论已关闭。

相关文章