1. 介绍
在项目中,调用第三方接口响应比较慢,或者由于网络抖动等原因,导致无响应的情况,就要用到重试机制.比较简单成熟的方案就是使用spring-retry功能,spring-retry需要使用aop的特性,所以引入aspectj。
2. 项目依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
3、spring对于重试机制的实现,给了几个抽象。
- BackOff:补偿值,一般指失败后多久进行重试的延迟值。
- Sleeper:暂停应用的工具,通常用来应用补偿值。
- BackOffPolicy:补偿策略,决定失败后如何确定补偿值。
- RetryContext:重试上下文,代表了能被重试动作使用的资源。
- RetryPolicy:重试策略,决定失败能否重试。
- RecoveryCallback:定义一个动作recover,在重试耗尽后的动作。
- RetryCallback:具体的重试动作。
- RetryOperations:通过传递RetryCallback,进行重试操作。
- RetryState:重试状态,通常包含一个重试的键值。
- RetryStatistics和RetryListener,用来监控Retry的执行情况,并生成统计信息。
4、代码示例
@Retryable(value= {Exception.class}, maxAttempts = 3) public void call() throws Exception { System.out.println("do something..."); throw new Exception("RPC调用异常"); } @Recover public void recover(RemoteAccessException e) { System.out.println(e.getMessage()); }
@Retryable(maxAttempts = 3, backoff = @Backoff(value = 3000, multiplier = 1.5)) public Customer getCustomer(String customerId) { if (true) { JSONArray data = retObj.getJSONArray("data"); if (data != null && !data.isEmpty()) { return data.toJavaList(Customer.class).get(0); } } else { log.error("异常,{}", customerId); throw new RuntimeException("获数据失败"); } return null; }
@Retryable被注解的方法发生异常时会重试。
@Retryable注解中的参数说明:
- maxAttempts :最大重试次数,默认为3,如果要设置的重试次数为3,可以不写;
- value:抛出指定异常才会重试
- include:和value一样,默认为空,当exclude也为空时,所有异常都重试
- exclude:指定不处理的异常,默认空,当include也为空时,所有异常都重试
- backoff:重试等待策略,默认使用@Backoff@Backoff的value默认为1000L,我们设置为2000L。
@Backoff重试补偿机制,默认没有
@Backoff注解中的参数说明:
- value:隔多少毫秒后重试,默认为1000L,我们设置为3000L;
- delay:和value一样,但是默认为0;
- multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
5、@Recover注解
可以在指定方法上标记@Recover来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中)
@Recover:
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。
6、采坑提示
1、由于retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效。参考改链接;
public class demo { public void A() { B(); } //这里B不会执行 @Retryable(Exception.class) public void B() { throw new RuntimeException("retry..."); } }
2、maxAttemps参数解释的是说重试次数,但是我再打断点的时候发现这个=1时,方法一共只执行了一次。
3、recover回调报错
org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method报错显示找不到recovery方法
解决recover回调报错的方案就这这两句话:
- 异常类型需要与Recover方法参数类型保持一致
- recover方法返回值需要与重试方法返回值保证一致
补充
对于非幂等的请求(比如新增,更新操作),千万不要使用重试,对数据一致性会造成很大影响。