拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Spring REST API设置请求超时

Spring REST API设置请求超时

白鹭 - 2021-11-24 632 0 0

1.概述

在本教程中,我们将探讨几种可能的方法来实现Spring REST API的请求超时。

我们将讨论每种方法的优缺点。请求超时对于防止不良的用户体验很有用,尤其是在存在资源占用时间过长的情况下,我们可以默认使用其他方法时。这种设计模式称为“断路器”模式,但在此不再赘述。

2. @ @Transactional超时

我们可以对数据库调用实现请求超时的一种方法是利用Spring的@Transactional注释。它具有我们可以设置的timeout属性。此属性的默认值为-1,这等效于根本没有任何超时。对于超时值的外部配置,必须使用另一个属性timeoutString代替。

例如,假设我们将此超时设置为30。如果带注释的方法的执行时间超过了此秒数,则将引发异常。这对于回滚长时间运行的数据库查询可能很有用。

为了了解这一点,让我们编写一个非常简单的JPA存储库层,该层将代表一个外部服务,该外部服务需要很长时间才能完成,并且会导致超时。此JpaRepository扩展中有一个耗时的方法:

public interface BookRepository extends JpaRepository<Book, String> {



 default int wasteTime() {

 int i = Integer.MIN_VALUE;

 while(i < Integer.MAX_VALUE) {

 i++;

 }

 return i;

 }

 }

如果我们在事务内部以1秒的超时时间调用wasteTime()方法,则超时将在该方法完成执行之前过去:

@GetMapping("/author/transactional")

 @Transactional(timeout = 1)

 public String getWithTransactionTimeout(@RequestParam String title) {

 bookRepository.wasteTime();

 return bookRepository.findById(title)

 .map(Book::getAuthor)

 .orElse("No book found for this title.");

 }

调用此端点会导致500 HTTP错误,我们可以将其转换为更有意义的响应。它还只需很少的设置即可实施。

但是,此超时解决方案有一些缺点。

首先,它依赖于具有Spring管理的事务的数据库。它也不是全局适用于项目的,因为注释必须出现在需要它的每个方法或类上。它还不允许亚秒精度。最后,在达到超时时,它不会缩短请求的时间,因此,请求实体仍然必须等待完整的时间。

让我们考虑其他一些选择。

3. Resilience4j TimeLimiter

Resilience4j是一个库,主要用于管理远程通信的容错能力。我们在这里感兴趣的是它的TimeLimiter模块

首先,我们必须在项目中包含resilience4j-timelimiter依赖项

<dependency>

 <groupId>io.github.resilience4j</groupId>

 <artifactId>resilience4j-timelimiter</artifactId>

 <version>1.6.1</version>

 </dependency>

接下来,让我们定义一个简单的TimeLimiter ,其超时时间为500毫秒:

private TimeLimiter ourTimeLimiter = TimeLimiter.of(TimeLimiterConfig.custom()

 .timeoutDuration(Duration.ofMillis(500)).build());

这可以很容易地在外部配置。

我们可以使用TimeLimiter来包装与@Transactional示例所使用的逻辑相同的逻辑:

@GetMapping("/author/resilience4j")

 public Callable<String> getWithResilience4jTimeLimiter(@RequestParam String title) {

 return TimeLimiter.decorateFutureSupplier(ourTimeLimiter, () ->

 CompletableFuture.supplyAsync(() -> {

 bookRepository.wasteTime();

 return bookRepository.findById(title)

 .map(Book::getAuthor)

 .orElse("No book found for this title.");

 }));

 }

@Transactional解决方案相比, TimeLimiter许多优点。即,它支持亚秒精度和超时响应的立即通知。但是,它仍然必须手动包含在所有需要超时的端点中,它需要一些冗长的包装代码,并且它产生的错误仍然是通用的500 HTTP错误。另外,它需要返回Callable<String>而不是原始String.

TimeLimiter仅包含Resilience4j的功能的子集,并且与Circuit Breaker模式很好地接口。

4. Spring MVC request-timeout

Spring为我们提供了一个名为spring.mvc.async.request-timeout的属性。此属性使我们可以定义毫秒级的请求超时。

让我们以750毫秒的超时时间定义属性:

spring.mvc.async.request-timeout=750

该属性是全局的,并且可以在外部配置,但是与TimeLimiter解决方案一样,它仅适用于返回Callable端点。让我们定义一个类似于TimeLimiter示例的端点,但是不需要将逻辑包装在Futures或提供TimeLimiter

@GetMapping("/author/mvc-request-timeout")

 public Callable<String> getWithMvcRequestTimeout(@RequestParam String title) {

 return () -> {

 bookRepository.wasteTime();

 return bookRepository.findById(title)

 .map(Book::getAuthor)

 .orElse("No book found for this title.");

 };

 }

我们可以看到代码不太冗长,并且在定义应用程序属性时,Spring会自动实现配置。一旦达到超时,响应将立即返回,它甚至返回更具描述性的503 HTTP错误,而不是通用的500错误。此外,我们项目中的每个端点都将自动继承此超时配置。

让我们考虑另一个选项,该选项使我们可以更详细地定义超时。

5. WebClient超时

与其为整个端点设置超时,不如我们只想为单个外部呼叫设置超时。 WebClient是Spring的反应式Web客户端,允许我们配置响应超时。

也可以在Spring较旧的RestTemplate对像上配置超时。但是,大多数开发人员现在更喜欢WebClient不是RestTemplate

要使用WebClient,我们必须首先将Spring的WebFlux依赖项添加到我们的项目中:

<dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-webflux</artifactId>

 <version>2.4.2</version>

 </dependency>

让我们定义一个响应时间为250毫秒的WebClient ,我们可以使用它在其基本URL中通过localhost进行调用:

@Bean

 public WebClient webClient() {

 return WebClient.builder()

 .baseUrl("http://localhost:8080")

 .clientConnector(new ReactorClientHttpConnector(

 HttpClient.create().responseTimeout(Duration.ofMillis(250))

 ))

 .build();

 }

显然,我们可以在外部轻松配置此超时值。我们还可以在外部配置基本URL以及其他几个可选属性。

现在,我们可以将WebClient注入到控制器中,并使用它来调用自己的/transactional端点,该端点的超时时间仍为1秒。因为我们将WebClient配置为在250毫秒内超时,所以我们应该看到它失败的速度比1秒快得多。

这是我们的新端点:

@GetMapping("/author/webclient")

 public String getWithWebClient(@RequestParam String title) {

 return webClient.get()

 .uri(uriBuilder -> uriBuilder

 .path("/author/transactional")

 .queryParam("title", title)

 .build())

 .retrieve()

 .bodyToMono(String.class)

 .block();

 }

调用此终结点后,我们看到确实以500 HTTP错误响应的形式收到WebClient的超时。我们还可以检查日志以查看下游@Transactional超时。但是,当然,如果我们调用外部服务而不是本地主机,则它的超时将被远程打印。

为不同的后端服务配置不同的请求超时可能是必需的,并且使用此解决方案是可能的。此外, WebClient返回的MonoFlux响应发布者包含许多错误处理方法,用于处理通用超时错误响应。

六,结论

在本文中,我们刚刚探讨了几种用于实现请求超时的解决方案。在决定使用哪一个时,要考虑几个因素。

如果我们想对数据库请求设置超时,则可能要使用Spring的@Transactional方法及其timeout属性。如果我们试图与更广泛的断路器模式集成,则使用Resilience4j的TimeLimiter会很有意义。使用Spring MVC request-timeout属性最适合为所有请求设置全局超时,但是我们可以使用WebClient轻松为每个资源定义更精细的超时。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *