Java中重试机制

259 阅读4分钟

本文重点介绍Springspring-retryGuavaguava-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:自成体系,使用起来更加灵活强大

如果本文有误,请及时联系我改正哦

2B04A51F.png