如何在SpringBoot中优雅地重试调用第三方API?

文章讨论了后端开发者在处理第三方服务调用时遇到的超时问题,通常需要实现重试机制。作者通过一个示例展示了手动实现重试的代码,并指出这种方式不够优雅。然后介绍了Spring的spring-retry库,它提供了一种注解驱动的重试机制,可以在不改变原有业务逻辑的情况下实现优雅的重试功能。文章演示了如何配置spring-retry以及如何在服务中使用@Retryable注解来自动重试特定异常,例如超时。

前言


作为后端程序员,我们的日常工作就是调用一些第三方服务,将数据存入数据库,返回信息给前端。但你不能保证所有的事情一直都很顺利。像有些第三方API,偶尔会出现超时。此时,我们要重试几次,这取决于你的重试策略。

下面举一个我在日常开发中多次看到的例子:

publicinterfaceOutSource {
    List<Integer> getResult() throws TimeOutException;
}

@ServicepublicclassOutSourceImplimplementsOutSource {

    staticRandom random = newRandom();
    @OverridepublicList<Integer> getResult() {
        //mock failureif (random.nextInt(2) == 1)
            thrownewTimeOutException();
        returnList.of(1, 2, 3);
    }
}


@Slf4j@ServicepublicclassManuallyRetryService {

    @AutowiredprivateOutSource outSource;

    publicList<Integer> getOutSourceResult(String data, int retryTimes) {
        log.info("trigger time:{}", retryTimes);

        if (retryTimes > 3) {
            returnList.of();
        }

        try {
            List<Integer> lst = outSource.getResult();
            if (!CollectionUtils.isEmpty(lst)) {
                return lst;
            }

            log.error("getOutSourceResult error, data:{}", data);
        } catch (TimeOutException e) {
            log.error("getOutSourceResult timeout", e);
        }
        // 递归调用returngetOutSourceResult(data, retryTimes + 1);
    }

}

@Slf4j@RestControllerpublicclassRetryTestController {

    @AutowiredprivateManuallyRetryService manuallyRetryService;
    
    @GetMapping("manually")
    publicStringmanuallyRetry() {
        List<Integer> result = manuallyRetryService.getOutSourceResult("haha", 0);
        if (!CollectionUtils.isEmpty(result)) {
            return"ok";
        }
        return"fail";
    }
}
复制代码

看看上面这段代码,我认为它可以正常工作,当retryTimes达到4时,无论如何我们都会得到最终结果。但是你觉得写的好吗?优雅吗?下面我来介绍Spring中的一个组件:spring-retry,我们不妨来试一试。

Spring-Retry介绍使用


spring-retry是Spring中的提供的一个重试框架,提供了注解的方式,在不入侵原有业务逻辑代码的方式下,优雅的实现重处理功能。

安装依赖

  • 如果你的是gradle应用,引入下面的依赖

implementation 'org.springframework.boot:spring-boot-starter-aop''org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.retry:spring-retry'复制代码
  • 如果你的项目使用的是maven项目,引入下面的依赖

<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>复制代码

启用重试功能

添加@EnableRetry注解在入口的类上从而启用功能。

@SpringBootApplication//看过来@EnableRetry
public class TestSpringApplication {

    publicstaticvoidmain(String[] args) {
        SpringApplication.run(TestSpringApplication.class, args);
    }

}
复制代码

应用

我们以前面的为例,看看怎么使用,如下面的代码:

publicinterfaceOutSource {
    List<Integer> getResult() throws TimeOutException;
}

@ServicepublicclassOutSourceImplimplementsOutSource {

    staticRandom random = newRandom();
    @OverridepublicList<Integer> getResult() {
        //mock failure will throw an exception every timethrownewTimeOutException();
    }
}

@Slf4j@ServicepublicclassRetryableService {

    @AutowiredprivateOutSource outSource;

    // 看这里@Retryable(value = {TimeOutException.class}, maxAttempts = 3)
    publicList<Integer> getOutSourceResult(String data) {
        log.info("trigger timestamp:{}", System.currentTimeMillis() / 1000);

        List<Integer> lst = outSource.getResult();
        if (!CollectionUtils.isEmpty(lst)) {
            return lst;
        }
        log.error("getOutSourceResult error, data:{}", data);

        returnnull;
    }

}


@Slf4j@RestControllerpublicclassRetryTestController {

    @AutowiredprivateRetryableService retryableService;

    @GetMapping("retryable")
    publicStringmanuallyRetry2() {
        try {
            List<Integer> result = retryableService.getOutSourceResult("aaaa");
            if (!CollectionUtils.isEmpty(result)) {
                return"ok";
            }
        } catch (Exception e) {
            log.error("retryable final exception", e);
        }
        return"fail";
    }

}
复制代码
  • 关键在于Service层中的实现类中添加了 @Retryable注解,实现了重试, 指定value是TimeOutException异常会进行重试,最大重试maxAttempts3次。

验证

这一次,当我们访问http://localhost:8080/retryable时,我们将看到浏览器上的结果失败。然后在你的终端上看到:

INFO 66776 --- [nio-9997-exec-1] c.m.testspring.service.RetryableService  : trigger timestamp:1668236840
 INFO 66776 --- [nio-9997-exec-1] c.m.testspring.service.RetryableService  : trigger timestamp:1668236841
 INFO 66776 --- [nio-9997-exec-1] c.m.testspring.service.RetryableService  : trigger timestamp:1668236842
ERROR 66776 --- [nio-9997-exec-1] c.m.t.controller.RetryTestController     : retryable final exception
复制代码

总结


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值