1、背景介绍
随着互联网的发展项目中的业务功能越来越复杂,有一些基础服务我们不可避免的会去调用一些第三方的接口或者公司内其他项目中提供的服务,但是远程服务的健壮性和网络稳定性都是不可控因素。在测试阶段可能没有什么异常情况,但上线后可能会出现调用的接口因为内部错误或者网络波动而出错或返回系统异常,因此我们必须考虑加上重试机制。
重试机制可以提高系统的健壮性,并且减少因网络波动依赖服务临时不可用带来的影响,让系统能更稳定的运行。
2、测试环境
2.1 模拟远程调用
本文会用如下方法来模拟远程调用的服务,其中每调用3次才会成功一次:
@Slf4j
@Service
public class RemoteService {
/**
* 记录调用次数
*/
private final static AtomicLong count = new AtomicLong(0);
/**
* 每调用3次会成功一次
*/
public String hello() {
long current = count.incrementAndGet();
System.out.println("第" + current +"次被调用");
if (current % 3 != 0) {
log.warn("调用失败");
return "error";
}
return "success";
}
}
2.2 单元测试
编写单元测试:
@SpringBootTest
public class RemoteServiceTest {
@Autowired
private RemoteService remoteService;
@Test
public void hello() {
for (int i = 1; i < 9; i++) {
System.out.println("远程调用:" + remoteService.hello());
}
}
}
执行后查看结果:验证是否调用3次才成功一次
同时在上边的单元测试中用for循环进行失败重试:在调用的时候如果失败则会进行了重复调用,直到成功
@Test
public void testRetry() {
for (int i = 1; i < 9; i++) {
String result = remoteService.hello();
if (!result.equals("success")) {
System.out.println("调用失败");
continue;
}
System.out.println("远程调用成功");
break;
}
}
上述代码看上去可以解决问题,但实际上存在一些弊端:
- 由于没有重试间隔,很可能远程调用的服务还没有从网络异常中恢复,所以有可能接下来的几次调用都会失败
- 代码侵入式太高,调用方代码不够优雅
- 项目中远程调用的服务可能有很多,每个都去添加重试会出现大量的重复代码
3、自己动手使用AOP实现重试
考虑到以后可能会有很多的方法也需要重试功能,咱们可以将重试这个共性功能通过AOP来实现:
使用AOP来为目标调用设置切面,即可在目标方法调用前后添加一些重试的逻辑。
1)创建一个注解:用来标识需要重试的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retry {
/**
* 最多重试次数
*/
int attempts() default 3;
/**
* 重试间隔
*/
int interval() default 1;
}
2)在需要重试的方法上加上注解:
//指定重试次数和间隔
@Retry(attempts = 4, interval = 5)
public String hello() {
long current = count.incrementAndGet();
System.out.println("第" + current +"次被调用");
if (current % 3 != 0) {
log.warn("调用失败");
return "error";
}
return "success";
}
3)编写AOP切面类,引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>asp