本文重点介绍Spring中 spring-retry和Guava中guava-retry
使用场景举例:当需要调用第三方接口或进行远程调用时,可能会因网络波动导致请求失败。此时,我们可以采用重试机制,给请求几次尝试的机会。如果尝试次数用完仍失败,则确认请求失败。这样做可以提高第三方接口或远程调用接口的可靠性稳定性。
spring-retry
Spring-Retry是基于Spring AOP机制实现的,所以需要引入AOP依赖
添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
开启重试
@SpringBootApplication
@EnableRetry // 开启重试功能
public class BucketApplication {
public static void main(String[] args) {
SpringApplication.run(BucketApplication.class, args);
}
}
相关代码
@Service
@Slf4j
public class ReTryService {
// 重试的注解,当发生异常时,进行回调
@Retryable(value = Exception.class)
public String retryTempt(){
System.out.println("测试-value属性");
int a = 10 / 0;
return "test -retry";
}
@Recover
public String retryTempt(Throwable e){
log.info("全部重试失败,执行doRecover");
return "全部重试失败";
}
}
@RequestMapping("/retry")
@RestController
public class ReTryController {
@Autowired
private ReTryService reTryService;
@GetMapping
public String testRetry(){
return reTryService.retryTempt();
}
}
在需要重试的方法上添加 @Retryable 注解
@Retryable中每个属性代表含义解释如下:
value:
指定需要重试的异常类型。可以是具体的异常类,也可以是异常的数组。如果方法抛出这些指定的异常,则会触发重试。例如:@Retryable(value = {SomeException.class, AnotherException.class})
include:
指定需要包含在重试中的异常类型,即使它们是 exclude 列表中指定的异常类型的子类。
这个属性常常与 exclude 属性一起使用,以提供更精细的异常过滤。
exclude:
指定不应触发重试的异常类型。即使方法抛出这些异常,也不会进行重试。
例如:@Retryable(exclude = {SomeException.class}), 当include和exclude包含相同的异常类时,会以exclude为主
maxAttempts: 指定重试的最大次数。默认值为 3。 例如:@Retryable(maxAttempts = 5)
backoff: 用于定义重试之间的回退策略。可以是 Backoff 类型的对象,也可以是 ExponentialBackOffPolicy 或 FixedBackOffPolicy 的实例。 如果不指定,将使用默认的回退策略。
stateless: 指定重试操作是否应该是无状态的。当设置为 true 时,表明每次重试都是独立的,不依赖于之前的状态。默认值为 false。
openCircuits: 与 Hystrix 的断路器模式类似,当达到某个失败阈值时,可以停止重试并立即返回失败。这有助于防止系统被持续失败的服务调用所拖垮。
label: 为重试策略提供一个标签,方便在日志或监控中识别。 这些属性可以根据具体的应用场景和需求进行配置,以实现所需的重试逻辑。同时,Spring Retry 还提供了 @Recover 注解,用于定义当重试次数耗尽后应该执行的操作。
注意
- @Recover 注解标记的方法必须和被 @Retryable 标记的方法在同一个类中
- 重试方法抛出的异常类型需要与 recover() 方法参数类型保持一致
- recover() 方法返回值需要与重试方法返回值保证一致
- recover() 方法中不能再抛出 Exception,否则会报无法识别该异常的错误
由于 Spring Retry 用到了 Aspect 增强,所以就会有使用 Aspect 不可避免的坑——方法内部调用,如果被 @Retryable 注解的方法的调用方和被调用方处于同一个类中,那么重试将会失效- Spring的重试机制只支持对异常进行捕获,而无法对返回值进行校验
guava-retry
引入依赖
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
相关代码
public class BasicRetryExample {
public static void main(String[] args) {
Retryer<Void> retryer = RetryerBuilder.<Void>newBuilder()
//无论出现什么异常,都进行重试
.retryIfException()
//返回结果为null时,进行重试
.retryIfResult(result -> Objects.isNull(result))
//重试等待策略:等待 2s 后再进行重试
.withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
//重试停止策略:重试达到 3 次
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
// 可以监听到每次调用,可以进行其他操作
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
System.out.println("RetryListener: 第" + attempt.getAttemptNumber() + "次调用");
}
})
.build();
try {
retryer.call(() -> {
// 模拟一个可能会失败的操作
if (Math.random() > 0.5) {
throw new RuntimeException("Operation failed");
}
return null; // 返回null表示成功,这里仅作示例
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以根据自己的需求来配置不同的重试策略,可以解决spring-retry中无法根据返回结果来重试的痛点
总结:
-
如果是基于 Spring 的项目,使用 Spring Retry 的注解方式已经可以解决大部分问题
-
如果项目没有使用 Spring 相关框架,则适合使用 Google guava-retrying:自成体系,使用起来更加灵活强大
如果本文有误,请及时联系我改正哦