一、引言
在实际的应用中,我们经常需要调用第三方API来获取数据或执行某些操作。然而,由于网络不稳定、第三方服务异常等原因,API调用可能会失败。为了提高系统的稳定性和可靠性,我们通常会考虑实现重试机制。
Spring Retry为Spring应用程序提供了方法级别的重试机制,帮助开发者处理由于网络不稳定、服务不可用、数据库临时性问题、外部依赖故障等原因导致的瞬时性故障。通过配置重试策略,可以确保应用程序在特定情况下能够自动进行重试,从而提高系统的健壮性和可用性。
本文将深入探讨如何在Spring Boot项目中优雅地使用Spring Retry重试调用第三方API,并结合代码示例,展示具体实现方式。
二、重试机制的必要性
提高系统的可用性:在网络通信、数据库操作、远程服务调用等场景中,由于网络抖动、服务暂时不可用或超时等原因,单次操作可能会失败。引入重试机制可以在这些情况下自动重新尝试操作,从而提高系统的可用性和容错能力。
优化用户体验:对于用户来说,遇到操作失败时,他们期望系统能够自动处理并恢复,而不是需要他们手动重新尝试。重试机制可以在用户无感知的情况下自动进行,从而提供更好的用户体验。
减少人工干预:在没有重试机制的情况下,一旦操作失败,可能需要人工进行干预和修复。这不仅增加了运维成本,而且可能导致响应延迟。重试机制可以自动处理大多数可恢复的失败情况,减少人工干预的需要。
降低故障率:有些故障是暂时的,例如网络短暂中断或数据库连接超时。在这种情况下,立即重试操作很可能会成功。重试机制可以在这些情况下自动重试,从而降低故障率。
三、使用场景
远程调用和网络通信:由于网络不稳定或服务不可用,可能会出现连接问题,Spring Retry可以帮助处理这类问题。
数据库交互:执行SQL查询或更新操作时,数据库服务器可能会出现临时性的问题,如死锁或连接丢失,Spring Retry可以提供重试机制。
外部依赖:如果应用程序依赖于外部服务、硬件设备或其他不可控因素,而这些依赖可能会偶尔出现故障或不可用状态,Spring Retry可以确保应用程序能够自动进行重试。
并发控制:在多线程环境中,可能会出现竞争条件或并发问题,导致某些操作失败,Spring Retry可以帮助处理这类并发问题。
复杂的业务逻辑:某些业务逻辑可能需要多次尝试才能成功,Spring Retry提供了重试机制来支持这类业务逻辑。
四、Spring Retry在项目中应用
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
2. 在需要重试的方法上添加@Retryable注解,并指定可重试的异常类型、最大重试次数等参数
@Retryable(value = {SomeException.class}, maxAttempts = 2, backoff = @Backoff(delay = 2000))
@Override
public void testRetry() {
// 业务场景
System.out.println("执行业务了...");
// 模拟执行可能会失败的操作 ...
throw new SomeException("run failed"); // 示例:抛出异常以触发重试
}
// 定义自定义异常类(如果需要)
public static class SomeException extends RuntimeException {
public SomeException(String message) {
super(message);
}
}
@RestController
public class RetryController {
@Autowired
private RetryService retryService;
@RequestMapping("/retry/test")
public Object retryTest() {
retryService.testRetry();
return "success";
}
}
3. 运行结果
4. 注解说明
@Retryable 注解
value:可重试的异常类型。含义同include。默认为空(如果excludes也为空,则重试所有异常)
include:可重试的异常类型。默认为空(如果excludes也为空,则重试所有异常)
exclude:无需重试的异常类型。默认为空(如果includes也为空,则重试所有异常)
maxAttempts:最大重试次数(包括第一次失败),默认为3次
backoff:重试等待策略,下面会在@Backoff中介绍
recover:表示重试次数到达最大重试次数后的回调方法
@Backoff 注解
value
默认为1000L,重试之间的基础延迟时间(以毫秒为单位)。当没有设置 delay时,value将作为首次重试的延迟时间。之后,如果multiplier不为0,则后续的延迟时间会根据 value和multiplier来计算
delay
默认为0,首次重试之前的延迟时间(以毫秒为单位)。如果设置为0,则不等待直接进行首次重试。如果同时设置了value和delay,则delay优先。
maxDelay
默认为0,重试之间延迟时间的上限(以毫秒为单位)。如果计算出的延迟时间超过了这个值,则实际的延迟时间将被限制为maxDelay。这个参数用于防止延迟时间无限增长,从而避免过长的等待时间。
multipler
默认为0.0。用于计算递增等待时间的乘数。例如,如果设置为1.5,则每次重试的等待时间将是前一次的1.5倍。
示例:backoff = @Backoff(delay = 200, multiplier = 1.5),在这个例子中,首次重试会等待200毫秒,第二次会等待300毫秒(200毫秒1.5),第三次会等待450毫秒(300毫秒1.5),以此类推。
delayExpression
评估标准退避期的表达式。在指数情况下用作初始值*,在均匀情况下用作最小值。覆盖 delay。
maxDelayExpression
该表达式计算重试之间的最大等待时间(以毫秒为单位)。 如果小于delay,那么将应用30000L为默认值。覆盖maxDelay。
multiplierExpression
评估为用作乘数的值,以生成退避的下一个延迟。覆盖multiplier。 返回一个乘数表达式,用于计算下一个退避延迟
random
默认为false,在指数情况下multiplier > 0将此值设置为true可以使后退延迟随机化,从而使最大延迟乘以前一延迟,并且两个值之间的分布是均匀的。
五、重试还是失败,如何解决?
除了重试,我们可能还希望在多次重试失败后执行降级操作,以避免一直等待不确定的恢复时间。
@Recover注解
可以使用@Recover注解来指定一个方法来处理所有由@Retryable注解标注的方法抛出的异常。当重试次数耗尽后,会调用这个方法
@Recover
public void recover(SomeException e) {
// 处理重试失败后的逻辑
System.out.println("recover from " + e.getMessage());
}
注意:
由于Spring Retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效
public void A(){
B();
}
//这里B不会执行
@Retryable(value = {SomeException.class}, maxAttempts = 2, backoff = @Backoff(delay = 2000))
public void B(){
throw new SomeException("run failed");
}
@Recover 注解来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中),此注解注释的方法参数一定要是 @Retryable 抛出的异常,否则无法识别。
六、重试当中的策略
1. @Backoff 的参数会影响我们使用哪种退避策略
FixedBackOffPolicy:默认退避策略,每1秒重试1次
ExponentialBackOffPolicy:指数退避策略,当设置multiplier时使用,每次重试时间间隔为 当前延迟时间 * multiplier。
例如:默认初始1秒,系数是2,那么下次延迟2秒,再下次就是延迟4秒,如此类推,最大30秒。
ExponentialRandomBackOffPolicy:指数随机退避策略。在指数退避策略的基础上增加了随机性。
UniformRandomBackOffPolicy:均匀随机策略,设置maxDely但没有设置multiplier时使用,重试间隔会在maxDelay和delay间随机
2. 重试策略
SimpleRetryPolicy:默认最多重试 3 次
TimeoutRetryPolicy:默认在 1 秒内失败都会重试
ExpressionRetryPolicy:符合表达式就会重试
CircuitBreakerRetryPolicy:增加了熔断的机制,如果不在熔断状态,则允许重试
CompositeRetryPolicy:可以组合多个重试策略
NeverRetryPolicy:从不重试(也是一种重试策略哈)
AlwaysRetryPolicy:总是重试
3. RetryTemplate提供了更为灵活的重试控制
了解了重试策略,当我们使用RetryTemplate模式的时候,可以更加灵活的重试控制
@Configuration
@EnableRetry
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
}
public Object executeWithRetry() throws Exception{
return retryTemplate.execute(new RetryCallback<Object, Exception>() {
@Override
public Object doWithRetry(RetryContext context) throws Exception {
// 这里放置可能会失败的操作
// 例如,调用远程服务或执行数据库操作
// ...
// 模拟失败
if (true) {
throw new RuntimeException("Operation failed");
}
// 如果成功,则返回结果
return "success";
}
});
}
在Spring Boot项目中,通过集成Spring Retry模块,我们可以优雅地实现对第三方API调用的重试机制。通过@Retryable注解,我们能够很方便地在方法级别上添加重试策略。