你好呀,我是小羊。
在我们平时工作中,有时候会有一些需要重试的场景,大家一般怎么处理这种需求呢?硬编码还是递归?今天介绍一种非常简单的处理方式,一起来看看吧。
1.递归实现重试需求
业务需求中比如调用一个外部接口,有可能调用成功失败,毕竟实际生产环境会有很多不确定因素,比如网络延时了,或者数据库查询慢了,或者下游接口熔断了等等。如果调用失败了,需要我们这边重试3次,如果重试成功了,就继续后面的逻辑。
实现这段逻辑很简单,比如下面使用递归的方式来做这个功能。
@Service
public class TestService {
@Autowired
private Test1Service test1Service;
public void postData(int retryNum){
boolean result = test1Service.add();
if (!result && retryNum<3) {
this.postData(retryNum+1);
}
}
}
上面的代码实现起来也很简单,先调用一下test1Service.add() 方法,判断一下结果和重试次数,如果失败了并且重试次数小于3,就继续调用 test1Service.add() 方法。并且把重试次数加一。功能基本上没有什么问题,后面需求变更,如果需要在重试的时候加上一些等待时间。你也想到了方案,加一个Thread.sleep() 即可。
@Service
public class TestService {
@Autowired
private Test1Service test1Service;
public void postData(int retryNum) throws InterruptedException {
boolean result = test1Service.add();
if (!result && retryNum<3) {
Thread.sleep(500);
this.postData(retryNum+1);
}
}
}
如果后面还需要加需求,每次重试的时间都不一样。实现也没有问题,加一个随机数就行
@Service
public class TestService {
@Autowired
private Test1Service test1Service;
public void postData(int retryNum) throws InterruptedException {
boolean result = test1Service.add();
if (!result && retryNum<3) {
Random random = new Random();
Thread.sleep(random.nextInt(100));
this.postData(retryNum+1);
}
}
}
递归虽然可以实现重试功能,但是代码有点啰嗦,有很多重试的逻辑,也不太方便阅读。
2.@Retryable注解简介
@Retryable是Spring Framework中的一个注解,用于实现方法的重试机制。当一个方法使用@Retryable注解标记时,Spring会在方法执行时捕获指定的异常,并在发生异常时尝试重新执行该方法,直到满足指定的重试条件为止。
@Retryable注解提供了一些属性,可以用来指定重试的条件,包括重试次数、重试的延迟时间、重试的异常类型等。通过这些属性,可以灵活地配置方法的重试行为。
@Retryable注解通常与@EnableRetry注解一起使用,@EnableRetry注解用于启用Spring的重试机制。
3.@Retryable使用方法
添加maven 依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
开启重试,启动类增加@EnableRetry 注解
@SpringBootApplication
@EnableRetry
public class SpringbootRetryableApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRetryableApplication.class, args);
}
}
在需要重试的方法增加 @Retryable 即可实现重试
@Retryable()
public void testFailRetry(String test) throws IOException {
log.info("测试4"+test);
throw new IllegalArgumentException("test4");
}
然后调用这个方法,抛出异常就可以让方法重试了。

默认是所有异常都会重试的,我们可以指定或者忽略一些异常重试。
@Retryable(include= IllegalArgumentException.class)
或者忽略一些异常
@Retryable(exclude= NullPointerException.class)
@Retryable 注解有以下常用参数:
value:指定需要进行重试的异常类型。默认情况下,会对所有异常进行重试。你可以通过设置 value 参数来指定需要重试的异常类型,例如 @Retryable(value = {CustomException.class})。
maxAttempts:指定最大重试次数。默认值为 3。你可以通过设置 maxAttempts 参数来指定重试的最大次数,例如 @Retryable(maxAttempts = 5)。
backoff:指定重试的退避策略。默认情况下,退避策略是固定间隔。你可以通过设置 backoff 参数来指定不同的退避策略。例如,@Retryable(backoff = @Backoff(delay = 1000, multiplier = 2.0)) 表示使用指数退避策略,初始延迟为 1000 毫秒,乘数为 2.0。
include 和 exclude:用于指定需要包含或排除的异常类型。include 参数用于指定需要重试的异常类型,exclude 参数用于指定不需要重试的异常类型。例如,@Retryable(include = {CustomException.class}, exclude = {IllegalArgumentException.class}) 表示只对 CustomException 异常进行重试,而不对 IllegalArgumentException 异常进行重试。
比如下面这个配置,空指针异常进行重试,并且重试5次,以100ms 为基数,随机间隔重试。
@Retryable(include= NullPointerException.class, maxAttempts = 5,backoff = @Backoff(delay = 100, multiplier = 2.0,random = true))
对于重试之后还是失败的情况,可以使用 @Recover 注解捕获失败,做一些兜底的操作
@Slf4j
public class ParentService {
@Recover
public void save(Exception e,String test) throws Exception {
log.info("回调"+test+this.getClass());
if (this.getClass().equals(TestService.class)){
log.info("TestService");
} else if(this.getClass().equals(Test2Service.class)){
log.info("Test2Service");
}else if(this.getClass().equals(Test4Service.class)){
log.info("Test4Service");
}
throw e;
}
}
4.@Retryable 注意事项
需要注意一些情况下 @Retryable 会失效:
-
重试方法不是公共方法:@Retryable注解只能应用于公共方法(public method),如果你尝试在私有方法或者受保护的方法上使用@Retryable注解,重试机制将不会生效。
-
重试方法是静态方法:@Retryable注解也不支持静态方法,因为它是基于代理对象来实现的,而静态方法无法被代理。
-
重试方法与调用它的非重试方法在同一个类中。
-
代码中捕获了异常,没有抛出。
-
@Retryable 是通过aop 实现的,如果进入类的第一个方法没有加@Retryable 注解,后面的方法加了注解,也是会失效的。
public void testFailRetry(String test) throws IOException {
testFailRetry1(test);
}
@Retryable()
public void testFailRetry1(String test) throws IOException {
log.info("测试5"+test);
throw new IllegalArgumentException("test5");
}
5.总结
@Retryable注解提供了方法级别的重试机制,它的使用有以下几个优点:
-
提高系统可靠性:当方法执行失败时,使用重试机制可以在一定程度上提高系统的可靠性。有些临时性的故障可能只是偶发的,通过重试可以让系统在故障恢复后自动继续执行,而无需手动干预。
-
减少对外部服务的影响:当应用程序依赖于外部服务时,外部服务的故障可能会导致应用程序的部分功能失效。通过重试机制,可以在外部服务恢复正常后自动重试受影响的方法,减少对外部服务故障的影响。
-
降低错误处理复杂度:有些错误可能是临时性的,例如网络超时、数据库连接异常等。通过重试机制,可以避免编写复杂的错误处理逻辑,而是让系统自动尝试恢复。
-
避免过度重试:通过@Retryable注解的属性配置,可以灵活地控制重试的次数、延迟时间等,避免过度重试导致系统负载过重或者对外部服务造成过大压力。
6.代码
https://gitee.com/yangzheng1/springboot-retryable
7.往期精彩推荐




原文始发于微信公众号(小羊架构):一个注解搞定失败重试,优雅又方便,超级简单
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/260082.html