Spring 接口重试机制

背景

大家在做项目的时候,往往会遇到一些接口由于网络抖动等问题导致接口响应超时等,这时候我们会希望能够按照一定的规则进行接口
请求重试。

分析

一般情况下,以上描述的情况,我们可能需要后台的定时任务去重新发起调用,以达到目的,这样无疑会增加开发成本,并且还得考虑
请求报文的保存等等问题。这时我们可以使用Spring提供的功能来完成这个需求。

实现

假设我们现在有一个接口,在这个接口流程中会出现一些异常,比如超时异常(数据库的异常、远程调用的异常等),出现这样的异常
我们就希望能够自动发起重新调用的功能。

创建工程

我们为了测试方便就直接创建一个简单的Spring Boot的工程就可以了。创建的时候引入如下依赖:

   <dependency>
       <groupId>org.springframework.retry</groupId>
       <artifactId>spring-retry</artifactId>
   </dependency>
   <!--AOP依赖-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-aop</artifactId>
   </dependency>

说明:由于Spring重试机制的基于注解的AOP实现,所以我们需要映入AOP的依赖,我们是Spring Boot项目直接使用AOP的启动器就可以了。

启动注解

由于我们是继续Spring Boot来开发这部分代码,所以我们需要配置开启重试机制的的注解,代码如下:

@SpringBootApplication
@EnableRetry
public class SpringbootApplication {

 public static void main(String[] args) {
  SpringApplication.run(SpringbootApplication.class, args);
 }
 
}

说明:Spring重试机制主要是使用注解@Retryable来实现的

@Retryable

该注解的源码如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
    String recover() default "";

    String interceptor() default "";

    Class<? extends Throwable>[] value() default {};

    Class<? extends Throwable>[] include() default {};

    Class<? extends Throwable>[] exclude() default {};

    String label() default "";

    boolean stateful() default false;

    int maxAttempts() default 3;

    String maxAttemptsExpression() default "";

    Backoff backoff() default @Backoff;

    String exceptionExpression() default "";

    String[] listeners() default {};
}

说明:

 value:抛出指定异常才会重试

 include:和value一样,默认为空,当exclude也为空时,默认所有异常

 exclude:指定不处理的异常
 
 maxAttempts:最大重试次数,默认3次
 
 backoff:重试等待策略, 默认使用@Backoff,@Backoff的value默认为1000, 以毫秒为单位的延迟(默认 1000)
 
 multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
注解@Retryable切面类

该注解主要的切面拦截器如下代码,感兴趣的小伙伴可以自行查看源码,分析。
AnnotationAwareRetryOperationsInterceptor#invoke()

@Recover

Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。
可以看到传参里面写的是Exception e,这个是作为回调的标识(重试次数用完了,还是失败,我们抛出这个Exception e通知触发这个回调方法)。

注意事项:

1、方法的返回值必须与@Retryable方法一致 

2、方法的第一个参数,必须是Throwable类型的,建议是与@Retryable配置的异常一致,其他的参数,需要哪个参数,写进去就可以了(@Recover方法中有的) 

3、该回调方法与重试方法写在同一个实现类里面 

4、由于是基于AOP实现,所以不支持类里自调用方法 

5、如果重试失败需要给@Recover注解的方法做后续处理,那这个重试的方法不能有返回值,只能是void 

6、方法内不能使用try catch,只能往外抛异常 

7、@Recover注解来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中),此注解注释的方法参数一定要是@Retryable抛出的异常。

该注解的代码如下:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Recover {

}
注解@Recover切面类

AnnotationAwareRetryOperationsInterceptor#getRecoverer()

写一个Demo

创建接口

首先我们创建一个接口。代码如下:

public interface RetryService {

    /**
     * 重试方法
     * @param str
     * @return
     * @throws Exception
     */
    String retry(String str) throws Exception;

    /**
     * 回调方法
     * @param e
     * @param str
     * @return
     */
    String recover(Exception e,String str);
}
接口实现

上述接口的实现的代码如下:

@Service
@Slf4j
public class RetryServiceImpl implements RetryService {
    /**
     * 重试方法
     * @param str
     * @return
     */
    @Override
    @Retryable(value = Exception.class,maxAttempts = 5,backoff = @Backoff(delay = 2000,multiplier = 1.5))
    public String retry(String str) throws Exception {
        log.info("Service 请求入参为:{}",str);
        log.info("进入测试方法,目前时间为:{}",new Date());
        if ("succ".equals(str)){
            return "succ";
        }else {
            throw new Exception("异常了!");
        }

    }

    /**
     * 重试次数完成后,回调的方法
     * @param e
     * @param str
     * @return
     */
    @Override
    @Recover
    public String recover(Exception e, String str) {
        log.info("异常出现后的回调操作,入参为:{},当前时间为:{}", str,LocalDate.now());
        return null;
    }
}

测试

我们使用postman进行测试

创建测试类

我们创建一个控制器来测试功能。代码如下:

@RestController
@Slf4j
public class RetryController {
    @Resource
    RetryService retryService;
    @GetMapping("/re")
    public String retry(@RequestParam("str") String str) throws Exception{
        log.info("Controller 请求入参为:{}",str);
        return retryService.retry(str);
    }
}

说明:我们的入参是一个字符串,若传入的字符串非succ那么手动抛出异常,我们观察日志,看是否框架自动发起了重试。

测试

我们启动Sping Boot项目,并且进行测试,来看一下效果。

使用postman请求我们的login方法,我们观察一下日志。请求地址localhost:8111/re,日志如下:


2023-07-17 21:05:19.488  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.controller.RetryController       : Controller 请求入参为:111
2023-07-17 21:05:19.516  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : Service 请求入参为:111
2023-07-17 21:05:19.516  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : 进入测试方法,目前时间为:Mon Jul 17 21:05:19 CST 2023
2023-07-17 21:05:21.518  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : Service 请求入参为:111
2023-07-17 21:05:21.519  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : 进入测试方法,目前时间为:Mon Jul 17 21:05:21 CST 2023
2023-07-17 21:05:24.522  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : Service 请求入参为:111
2023-07-17 21:05:24.523  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : 进入测试方法,目前时间为:Mon Jul 17 21:05:24 CST 2023
2023-07-17 21:05:29.024  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : Service 请求入参为:111
2023-07-17 21:05:29.025  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : 进入测试方法,目前时间为:Mon Jul 17 21:05:29 CST 2023
2023-07-17 21:05:35.779  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : Service 请求入参为:111
2023-07-17 21:05:35.779  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : 进入测试方法,目前时间为:Mon Jul 17 21:05:35 CST 2023
2023-07-17 21:05:35.790  INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl    : 异常出现后的回调操作,入参为:111,当前时间为:2023-07-17

可以看到,出现异常后框架自动发起了重试,在重试次数使用完成后,回调了异常处理的方法。

小结

本篇文章,我们简单的在实现层面整理了使用Spring-Retry的重试机制,后续有机会带着大家分析一下这部分源码,详细代码,大家可以直接进行下载使用源码地址
希望对大家有所帮助。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值