环境:SpringBoot3.4.0
1. 简介
在开发API接口时,配置API接口的超时时间是一项非常重要的任务。超时时间的设置对于确保系统的稳定性和响应性至关重要。当客户端发送请求到服务器时,如果服务器由于某些原因(如数据库查询缓慢、外部服务调用失败等)无法及时响应,那么设置合理的超时时间可以防止服务器资源被长时间占用,从而避免系统崩溃或性能下降。
在Spring Boot中,有多种方式可以配置API接口的超时时间。针对不同的应用场景,选择正确的配置方式,可以确保系统在面对各种复杂场景时都能保持高效和稳定。
本篇文章将通过的介绍如下几种API超时情况的配置:
- 事务超时时间配置
如果你的当前的API接口涉及到事务相关,那么我们可以通过设置设置的超时时间来确保由于数据库缓慢要引起的超时情况。 - 基于Resilience4j的超时保护机制
我们可以通过Resilience4j提供的超时机制来设置有效的超时时间。 - 异步请求超时
如果你当前请求是异步请求,那么我们可以通过配置异步超时时间来限制接口等待时间。 - HTTP Client超时配置
我们将介绍3种HTTP Client超时的配置,分别是RestTemplate,RestClient,WebClient。 - 基于NGINX代理超时配置
通常我们的后端接口一般会通过NGINX进行反向代理,在这种情况下,我们可以在其代理上配置超时时间。
2. 实战案例
2.1 事务超时配置
我们可以在数据库调用中实现请求超时的一种方法是利用Spring的@Transactional注解。该注解具有一个可以设置的超时属性。该属性的默认值为-1,相当于没有设置任何超时。
@Transactional(timeout = 1)
public List<User> query() {
this.userRepository.findById(8L).ifPresent(System.out::println) ;
try {
TimeUnit.SECONDS.sleep(1) ;
} catch (InterruptedException e) {}
return this.userRepository.findAll() ;
}
如上配置注解中配置超时时间为1s,内部执行时先根据id查询,此时能正常执行,当休眠1s后,再次执行数据库操作将抛出超时异常。
首先,我们进行如下异常配置:
@ExceptionHandler(TransactionTimedOutException.class)
public ResponseEntity<Object> txTimeout(TransactionTimedOutException ex) {
return ResponseEntity.ok("请求超时: " + ex.getMessage()) ;
}
测试接口
@GetMapping
public ResponseEntity<Object> query() {
return ResponseEntity.ok(this.userService.query()) ;
}
测试结果
图片
以上我们利用了事务的超时时间来保护接口。
2.2 基于Resilience4j的超时保护机制
Resilience4j提供了一个TimeLimiter模块,专门用来处理超时保护的。
首先,引入下面依赖:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>
接下来,通过注解配置需要保护的接口
@TimeLimiter(name = "queryUser", fallbackMethod = "fallbackQuery")
@GetMapping
public CompletionStage<ResponseEntity<Object>> query() {
return CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
TimeUnit.SECONDS.sleep(2) ;
} catch (InterruptedException e) {}
return ResponseEntity.ok("success") ;
}) ;
}
public CompletionStage<ResponseEntity<Object>> fallbackQuery(Throwable e) {
return CompletableFuture.completedStage(ResponseEntity.ok(e.getMessage())) ;
}
说明:
name:在配置文件中定义的超时相关配置,如果配置文件中没有配置则使用默认的配置。
fallbackMethod:当发生超时现象将调用的降级方法。
注意:方法的返回值必须是CompletionStage类型。
最后,配置超时
resilience4j:
timelimiter:
instances:
#该名称为上面注解中的name
queryUser:
timeout-duration: 1s
测试结果
图片
此种方式是不是非常的简单好用,一个注解搞定。
2.3 异步请求超时配置
当我们的API接口是异步请求时,我们可以直接在配置文件中对异步请求的超时时间进行配置:
spring:
mvc:
async:
request-timeout: 1s
异步请求接口
@GetMapping("/async")
public Callable<String> async() {
return () -> {
try {
TimeUnit.SECONDS.sleep(10) ;
} catch (InterruptedException e) {
return "任务中断 - " + e.getMessage() ;
}
return "异步请求成功" ;
} ;
}
测试结果
图片
虽然这里休眠了10s,但在1s后,直接输出了异常信息。
2.4 HTTP Client超时配置
这里我们将介绍3种接口调用的超时配置,分别是:RestTemplate,RestClient已经WebClient,其中RestTemplate与RestClient是基于阻塞式调用并且RestClient是Spring6.1版本开始提供的;而WebClient则是基于响应式的调用(非阻塞)。官方推荐使用WebClient。
RestTemplate超时配置
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
// 连接超时配置
.connectTimeout(Duration.ofSeconds(1))
// 读取超时配置
.readTimeout(Duration.ofSeconds(1))
.build() ;
}
这是最简单的配置,你还可以通过如下工厂方式配置
@Bean
RestTemplate restTemplate() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(1))
.withReadTimeout(Duration.ofSeconds(1));
RestTemplate restTemplate = new RestTemplate(
ClientHttpRequestFactoryBuilder.detect().build(settings)) ;
return restTemplate ;
}
根据你的环境选择不同的方式进行配置。
RestClient超时配置
RestClient的配置方式与上面的RestTemplate差不多。
@Bean
RestClient restClient() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(1))
.withReadTimeout(Duration.ofSeconds(1)) ;
return RestClient
.builder()
.requestFactory(ClientHttpRequestFactoryBuilder.detect().build(settings))
.build() ;
}
最后,我们再来介绍官方推荐的WebClient。
WebClient超时配置
首先,我们要引入以下的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
下面进行超时配置:
@Bean
WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(1))
.addHandlerLast(new WriteTimeoutHandler(1))) ;
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient ))
.build() ;
}
下面我们通过WebClient进行接口调用进行测试
访问接口
@GetMapping("/client")
public String client() {
try {
TimeUnit.SECONDS.sleep(3) ;
} catch (InterruptedException e) {
return "任务中断 - " + e.getMessage() ;
}
return "success" ;
}
通过WebClient访问该接口:
private final WebClient webClient ;
this.webClient
.get()
.uri("http://localhost:8080/api/users/client")
.retrieve()
.bodyToMono(String.class)
// 当发生错误时会自动调用该方法进行恢复继续执行
.onErrorResume(ex -> {
return Mono.just("发生错误: " + ex.getMessage()) ;
})
.subscribe(System.out::println) ;
测试结果:
io.netty.handler.timeout.ReadTimeoutException: null
发生错误: null
以上就是关于HTTP Client的超时配置。
2.5 基于NGINX代理超时配置
通过NGINX反向代理配置超时时间
location / {
proxy_pass http://127.0.0.1:8080;
proxy_connect_timeout 1s; # 连接超时时间为30秒
proxy_send_timeout 1s; # 发送请求超时时间为60秒
proxy_read_timeout 1s; # 读取响应超时时间为60秒
}
当发生超时时,我们这里通过日志查看:
[error] 11172#27080: *1 upstream timed out
(10060: A connection attempt failed because the connected
party did not properly respond after a period of time,
or established connection failed because connected
host has failed to respond) while reading
response header from upstream,
client: 127.0.0.1, server: localhost,
request: "GET /api/users/client HTTP/1.1",
upstream: "http://127.0.0.1:8080/api/users/client",
host: "localhost:1080"
当发生异常,我们还可以进行如下的配置,进行友好提示:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_connect_timeout 1s; # 连接超时时间为30秒
proxy_send_timeout 1s; # 发送请求超时时间为60秒
proxy_read_timeout 1s; # 读取响应超时时间为60秒
# 指定自定义错误页面
error_page 504 /timeout.html;
# 指定自定义错误页面的位置
location = /timeout.html {
root D:/all/html/;
internal;
}
}
图片