【问题标题】:Spring Boot REST API - request timeout?Spring Boot REST API - 请求超时?
【发布时间】:2016-04-23 11:26:51
【问题描述】:

我有一个 Spring Boot REST 服务,它有时会在请求中调用第三方服务。我想为我的所有资源设置一个超时时间(比如说 5 秒),这样如果任何请求处理(整个链,从传入到响应)花费的时间超过 5 秒,我的控制器就会使用 HTTP 503 而不是实际响应进行响应.如果这只是一个 Spring 属性,那就太棒了,例如设置

spring.mvc.async.request-timeout=5000

但我没有任何运气。我也尝试过扩展 WebMvcConfigurationSupport 并覆盖 configureAsyncSupport:

@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
    configurer.setDefaultTimeout(5000);
    configurer.registerCallableInterceptors(timeoutInterceptor());
}

@Bean
public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
    return new TimeoutCallableProcessingInterceptor();
}

运气不好。

我怀疑我必须手动为所有第三方调用计时,如果调用时间过长,则抛出超时异常。那正确吗?或者是否有任何更简单、更全面的解决方案涵盖我的所有请求端点?

【问题讨论】:

    标签: java spring rest spring-boot timeout


    【解决方案1】:

    如果你想让spring.mvc.async.request-timeout=5000 工作,你需要返回一个Callable<>

    @RequestMapping(method = RequestMethod.GET)
    public Callable<String> getFoobar() throws InterruptedException {
        return new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(8000); //this will cause a timeout
                return "foobar";
            }
        };
    }
    

    【讨论】:

    • 如果使用Java 8,也可以使用lamba表达式:return () -&gt; {/* do your stuff here*/};
    【解决方案2】:

    @Transactional 注解带有一个超时参数,您可以在其中为@RestController 中的特定方法指定超时(以秒为单位)

    @RequestMapping(value = "/method",
        method = RequestMethod.POST,
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    @Transactional(timeout = 120)
    

    【讨论】:

    • 有没有办法在 spring-boot 中全局设置事务超时?
    • @Transactional 旨在处理数据库事务行为,而不是为 REST API 操作添加执行时间限制。
    【解决方案3】:

    Spring Boot 2.2 需要一个全新的答案,因为 server.connection-timeout=5000 已弃用。每个服务器的行为都不同,因此建议使用特定于服务器的属性。

    SpringBoot 默认嵌入 Tomcat,如果您没有使用 Jetty 或其他东西重新配置它。 使用特定于服务器的应用程序属性,例如 server.tomcat.connection-timeoutserver.jetty.idle-timeout

    正如Wilkinson评论的那样:

    设置连接超时只会导致超时 客户端连接但太慢而无法发送其请求。如果你想 客户端最多等待 30 秒的响应,您将 必须在客户端进行配置。如果你想要服务器端 最多只花费 30 秒处理请求没有 保证您不能强制正在处理的线程的方法 请求停止。

    你也可以尝试设置spring.mvc.async.request-timeout

    【讨论】:

    • 我将所有 3 个用于使用 @RestController 的应用程序,并且我为它们中的每一个设置了非常低的值,但它们都被忽略了。即使请求需要更长的时间,页面也会正常提供。
    • 这不会限制您的回答,而是限制客户端在打开连接后向您发送请求所需的时间:请参阅此处github.com/spring-projects/spring-boot/issues/21914
    • 所以连接/空闲超时实际上是请求超时? ))
    【解决方案4】:

    我建议您查看 Spring Cloud Netflix Hystrix 启动器,以处理可能不可靠/缓慢的远程调用。它实现了断路器模式,正是为这种事情而设计的。

    offcial docs for more information

    【讨论】:

    • 我不明白为什么这被否决了,在我看来,实施断路器模式是最好的答案。否则,在保持请求运行的同时简单地返回 503,您可能会浪费资源
    • 你也可以使用resilience4j resilience4j.readme.io/docs/circuitbreaker。实际的 Netflix Hystrix 不再维护。
    【解决方案5】:

    您可以在 application.properties 中尝试server.connection-timeout=5000。来自official documentation

    server.connection-timeout= # 连接器将连接的时间(以毫秒为单位) 在关闭连接之前等待另一个 HTTP 请求。没有的时候 设置后,将使用连接器特定于容器的默认值。用一个 值为 -1 表示没有(即无限)超时。

    另一方面,您可能希望使用断路器模式在客户端处理超时,正如我在此处的回答中已经描述的那样:https://stackoverflow.com/a/44484579/2328781

    【讨论】:

    • server.connection-timeout=5000 - 已弃用 - 每个服务器的行为不同。请改用服务器特定属性。
    • @Zon 你知道基本 Spring Boot 2 应用程序的服务器特定属性吗?
    • @MozenRath,如果您没有使用 Jetty 或其他东西重新配置它,SpringBoot 默认嵌入 Tomcat。请改用服务器特定的应用程序属性,例如 server.tomcat.connection-timeoutserver.jetty.idle-timeout
    【解决方案6】:

    如果您使用的是 RestTemplate,那么您应该使用以下代码来实现超时

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(clientHttpRequestFactory());
    }
    
    private ClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(2000);
        factory.setConnectTimeout(2000);
        return factory;
    }}
    

    xml配置

    <bean class="org.springframework.web.client.RestTemplate">
    <constructor-arg>
        <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"
            p:readTimeout="2000"
            p:connectTimeout="2000" />
    </constructor-arg>
    

    【讨论】:

    【解决方案7】:

    在 Spring 属性文件中,您不能只为该属性指定一个数字。您还需要指定一个单位。所以你可以说spring.mvc.async.request-timeout=5000msspring.mvc.async.request-timeout=5s,两者都会给你一个5 秒的超时时间。

    【讨论】:

    • 您能详细说明一下吗?我试过了,没有单位就不行。
    • 查看其他一些答案或官方文档 (docs.spring.io/spring-boot/docs/current/reference/html/…)。该问题与SpringBoot有关。
    • 你的链接只是把我带到了源代码,它告诉我属性类型是java.time.Duration,它需要一个单位。我确实看过其他答案,但没有人讨论如何在属性文件中指定 spring.mvc.async.request-timeout 属性。他们都在代码中指定了它。据我所知,您链接到的文档支持我所说的。所以再一次。能详细点吗?
    • 查看 Danylo Zatorsky 的回答。或互联网上的任何其他人:stackoverflow.com/questions/47025525/…stackoverflow.com/questions/51006913/…
    • 所以你是说我说的是错误的属性?我的观点是,根据我的经验,返回 Duration 的属性需要指定单位。我只是以spring.mvc.async.request-timeout 属性为例,因为最初的提问者使用了它。你对这个说法有异议吗?还是您只是指要使用的正确属性。请自己回答我的问题,而不是把我指向其他地方。你的答案一直很模糊,直到最新的一个,这最终给了我一些更具体的东西。
    【解决方案8】:

    我觉得没有一个答案能真正解决问题。我认为您需要告诉 Spring Boot 的嵌入式服务器处理请求的最长时间应该是多少。我们如何做到这一点取决于所使用的嵌入式服务器的类型。

    如果是 Undertow,可以这样做:

    @Component
    class WebServerCustomizer : WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
        override fun customize(factory: UndertowServletWebServerFactory) {
            factory.addBuilderCustomizers(UndertowBuilderCustomizer {
                it.setSocketOption(Options.READ_TIMEOUT, 5000)
                it.setSocketOption(Options.WRITE_TIMEOUT, 25000)
            })
        }
    }
    

    Spring Boot 官方文档:https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/howto.html#howto-configure-webserver

    【讨论】:

      【解决方案9】:

      您可以为 Springboot REST 服务配置异步线程执行器。 setKeepAliveSeconds() 应该考虑请求链的执行时间。设置 ThreadPoolExecutor 的 keep-alive 秒数。默认值为 60。可以在运行时修改此设置,例如通过 JMX。

      @Bean(name="asyncExec")
      public Executor asyncExecutor()
      {
          ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
          executor.setCorePoolSize(3);
          executor.setMaxPoolSize(3);
          executor.setQueueCapacity(10);
          executor.setThreadNamePrefix("AsynchThread-");
          executor.setAllowCoreThreadTimeOut(true);
          executor.setKeepAliveSeconds(10);
          executor.initialize();
      
          return executor;
      }
      

      然后您可以如下定义您的 REST 端点

      @Async("asyncExec")
      @PostMapping("/delayedService")
      public CompletableFuture<String> doDelay()
      { 
          String response = service.callDelayedService();
          return CompletableFuture.completedFuture(response);
      }
      

      【讨论】:

        猜你喜欢
        • 2018-09-11
        • 2022-09-28
        • 2016-09-13
        • 2021-09-22
        • 1970-01-01
        • 2019-11-06
        • 1970-01-01
        • 1970-01-01
        • 2021-07-11
        相关资源
        最近更新 更多