又又又报错了:
发现淘宝和微信都是一个德行,对客端的页面和交互对用户各种谄媚,各种小惊喜,各种体验优化,各种暖心交互,各种闭环;对商端做的真是一坨屎,没有提供场景化完成一个功能的文档,满眼都是鸡零狗碎的东一个西一个的API,文档页面看着老旧丑陋没有技术含量,并且不好找,找个功能需要找半天,没有闭环想用个功能需要各种申请,烦死个人。权限开通后,一个不小心权限就被封功能不能用,又是各种申诉各种整改,憋屈。气死个人。
吐槽又不能解决问题。先来看看这个报错吧。好在淘宝还可以提工单,这点比微信更有助于解决遇到的问题。
淘宝说我就是这样,也不怕你重试,你再来多请求几次呀!!
既然淘宝方案给了,那就增加重试吧。
自己写一个for循环+sleep来完成重试有些slow+花时间,感觉还是用现成的组件进行重试更优雅。
之前做过技术选型,选了Spring Retry。Spring Retry就不在这展开介绍了,详见文末。
要在Spring框架中基于Spring-Retry实现接口抛异常后的Retryable功能,可以遵循以下步骤:
首先,你需要在你的Spring Boot项目的pom.xml文件中添加spring-retry的依赖。
<!-- 重试相关依赖包 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
默认是不开启的。
在你的Spring Boot应用的主启动类上添加@EnableRetry注解,以开启重试机制的支持。
@SpringBootApplication
@EnableRetry
public class RetryApplication {
public static void main(String[] args) {
SpringApplication.run(RetryApplication.class, args);
}
}
使用@Retryable注解:
在需要实现重试的方法上添加@Retryable注解,这样当方法抛出指定异常时,就会自动进行重试。
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import java.util.concurrent.ThreadLocalRandom;
@Service
@Slf4j
public class RetryServiceImpl implements RetryService {
@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 1.5))
@Override
public String doBiz() {
int countVal = ThreadLocalRandom.current().nextInt(10);
if (countVal > 1) {
// 这里可以使自定义异常,@Retryable中value需与其一致
log.warn("doBiz 执行业务代码失败 countVal {} ", countVal);
throw new RuntimeException("执行业务代码失败");
}
log.info("doBiz 执行业务代码成功 countVal {} ", countVal);
return "success";
}
}
}
翻译一下:
在上面的例子中,@Retryable
注解的参数含义如下:
value
:指定重试的异常类型,这里是Exception
,表示当抛出Exception
类型异常时会触发重试。maxAttempts
:最大重试次数,这里是3次(包括第一次尝试)。backoff
:重试的间隔策略,delay
指定了第一次重试的延迟时间(1000毫秒),multiplier
指定了延迟时间的增长倍数(1.5倍)。 启用详细的日志记录:
在Spring的配置中启用DEBUG级别的日志,这样可以查看Spring Retry的内部工作情况,包括代理创建和重试逻辑。可视化的看到重试的过程
# 在application.properties或application.yml中添加
logging.level.org.springframework.retry=DEBUG
写个测试用例,Run起来
import com.tangcheng.toolbox.ToolBoxApplication;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = ToolBoxApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
class RetryServiceImplTest {
@Autowired
private RetryService retryService;
@Test
void doBiz() {
String result = retryService.doBiz();
log.info("result {} ", result);
}
}
执行结果:
2024-12-10 21:21:24.749 INFO 22518 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 65177 (http) with context path ''
2024-12-10 21:21:24.760 INFO 22518 --- [ main] c.t.t.b.tool.RetryServiceImplTest : Started RetryServiceImplTest in 2.932 seconds (JVM running for 4.005)
2024-12-10 21:21:24.991 DEBUG 22518 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=0
2024-12-10 21:21:24.999 WARN 22518 --- [ main] c.t.t.business.tool.RetryServiceImpl : doBiz 执行业务代码失败 countVal 7
2024-12-10 21:21:24.999 DEBUG 22518 --- [ main] o.s.r.backoff.ExponentialBackOffPolicy : Sleeping for 1000
2024-12-10 21:21:26.004 DEBUG 22518 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=1
2024-12-10 21:21:26.004 DEBUG 22518 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=1
2024-12-10 21:21:26.004 INFO 22518 --- [ main] c.t.t.business.tool.RetryServiceImpl : doBiz 执行业务代码成功 countVal 0
2024-12-10 21:21:26.004 INFO 22518 --- [ main] c.t.t.b.tool.RetryServiceImplTest : result success
第二次重试时成功了
可以看到,Spring Retry的debug日志与业务代码中打印日志是一致的。
正常情况下,生产环境是不会开debug日志的,想在重试失败时进行一些操作,譬如打印特殊的日志,譬如发一条告警通知,需要怎么做?在加了@Retryable注解的方法中直接加,肯定是不行的。至于原因,各位大佬想一下为什么
实际上,这种场景Spring Retry也是支持的。你可以使用@Recover注解来指定一个方法,当重试次数耗尽后,这个方法将被调用以进行异常恢复处理。
@Recover注解用于标记一个方法,该方法会在@Retryable注解标记的方法重试后仍然失败时被调用。这个方法可以作为最后的防线,处理重试失败后的情况,比如记录日志、发送告警、或者返回一个默认值等。
@Recover
public String doBizRecover(Exception e) {
log.warn("@Recover回调方法执行 重试后仍然没有成功");
return "报错了 " + e.getMessage();
}
多跑几次测试用例,尝试触发重试后仍然失败的场景:
2024-12-10 21:31:08.452 INFO 24877 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 50004 (http) with context path ''
2024-12-10 21:31:08.462 INFO 24877 --- [ main] c.t.t.b.tool.RetryServiceImplTest : Started RetryServiceImplTest in 2.785 seconds (JVM running for 3.909)
2024-12-10 21:31:08.690 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=0
2024-12-10 21:31:08.696 WARN 24877 --- [ main] c.t.t.business.tool.RetryServiceImpl : doBiz 执行业务代码失败 countVal 3
2024-12-10 21:31:08.697 DEBUG 24877 --- [ main] o.s.r.backoff.ExponentialBackOffPolicy : Sleeping for 1000
2024-12-10 21:31:09.701 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=1
2024-12-10 21:31:09.701 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=1
2024-12-10 21:31:09.702 WARN 24877 --- [ main] c.t.t.business.tool.RetryServiceImpl : doBiz 执行业务代码失败 countVal 2
2024-12-10 21:31:09.702 DEBUG 24877 --- [ main] o.s.r.backoff.ExponentialBackOffPolicy : Sleeping for 1500
2024-12-10 21:31:11.207 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=2
2024-12-10 21:31:11.207 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=2
2024-12-10 21:31:11.208 WARN 24877 --- [ main] c.t.t.business.tool.RetryServiceImpl : doBiz 执行业务代码失败 countVal 4
2024-12-10 21:31:11.208 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=3
2024-12-10 21:31:11.208 DEBUG 24877 --- [ main] o.s.retry.support.RetryTemplate : Retry failed last attempt: count=3
2024-12-10 21:31:11.209 WARN 24877 --- [ main] c.t.t.business.tool.RetryServiceImpl : @Recover回调方法执行 重试后仍然没有成功
2024-12-10 21:31:11.209 INFO 24877 --- [ main] c.t.t.b.tool.RetryServiceImplTest : result 报错了 执行业务代码失败
Spring Retry 提供了一系列优点,使其成为处理暂时性故障和提高应用程序弹性的有力工具:
RetryListener
接口,可以在重试的不同阶段插入自定义逻辑,如记录日志、更新状态等。阿里P7大佬首次分享Spring Retry不为人知的技巧https://zhuanlan.zhihu.com/p/37625630