RestTemplate设置每个请求的超时

问题描述 投票:2回答:4

我有几种方法的@Service,每种方法都使用不同的web api。每次调用都应具有自定义读取超时。拥有一个RestTemplate实例并在每个方法中通过工厂更改超时是否是线程安全的

((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory())
.setReadTimeout(customMillis);

我担心的是我正在改变工厂的超时,而不是像RequestConfig。考虑到这些方法可能会被多个用户同时调用,这种方法是否会是线程安全的?或者每种方法都应该有自己的RestTemplate

spring spring-boot resttemplate
4个回答
2
投票

我假设您希望读取超时,以防响应时间过长。

一种可能的解决方案是通过在给定时间内未完成请求时取消请求来自己实现超时。

要实现这一点,您可以使用qazxsw poi,它内置支持异步操作,如超时和取消。

这使您可以更好地控制每个请求的超时,例如:

AsyncRestTemplate

2
投票

选项1:多个RestTemplate

如果要更改创建的连接的属性,则每个配置需要一个ListenableFuture<ResponseEntity<Potato>> future = asyncRestTemplate.getForEntity(url, Potato.class); ResponseEntity<Potato> response = future.get(5, TimeUnit.SECONDS); 。我最近遇到了同样的问题,有两个版本的RestTemplate,一个用于“短暂超时”,另一个用于“长时间超时”。在每组(短/长)内,我能够分享那个RestTemplate

让您的呼叫更改超时设置,创建连接,并希望最好的是等待发生的竞争条件。我会玩这个安全并创造不止一个RestTemplate

例:

RestTemplate

然后,您可以根据需要将它们连接到您的服务:

@Configuration
public class RestTemplateConfigs {
    @Bean("shortTimeoutRestTemplate")
    public RestTemplate shortTimeoutRestTemplate() {
       // Create template with short timeout, see docs.
    }
    @Bean("longTimeoutRestTemplate")
    public RestTemplate longTimeoutRestTemplate() {
       // Create template with short timeout, see docs.
    }
}

选项2:在断路器中包裹呼叫

如果你打电话给外部服务,你可能需要@Service public class MyService { private final RestTemplate shortTimeout; private final RestTemplate longTimeout; @Autowired public MyService(@Qualifier("shortTimeoutRestTemplate") RestTemplate shortTimeout, @Qualifier("longTimeoutRestTemplate") RestTemplate longTimeout) { this.shortTimeout = shortTimeout; this.longTimeout = longTimeout; } // Your business methods here... } 。 Spring Boot与Hystrix配合使用,Hystrix是断路器模式的流行实现。使用hystrix,您可以控制调出的每个服务的回退以及超时。

假设您有两个服务A选项:1)便宜但有时慢2)昂贵但快速。您可以使用Hystrix放弃Cheap / Slow并在需要时使用Expensive / Fast。或者你可以没有备份,只是让Hystrix调用一个提供合理默认值的方法。

未经测试的例子:

should be using a circuit breaker

后备方法也有选项。您可以使用@EnableCircuitBreaker public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp .class, args); } } @Service public class MyService { private final RestTemplate restTemplate; public BookService(RestTemplate rest) { this.restTemplate = rest; } @HystrixCommand( fallbackMethod = "fooMethodFallback", commandProperties = { @HystrixProperty( name = "execution.isolation.thread.timeoutInMilliseconds", value="5000" ) } ) public String fooMethod() { // Your logic here. restTemplate.exchange(...); } public String fooMethodFallback(Throwable t) { log.error("Fallback happened", t); return "Sensible Default Here!" } } 注释该方法并尝试另一个服务调用。或者,您可以提供合理的默认值。


2
投票

@HystrixCommand初始化之后更改工厂的超时只是一种等待发生的竞争条件(如Todd RestTemplate)。 explained真的被设计为使用预先配置的超时构建,并且这些超时在初始化后保持不变。如果您使用RestTemplate然后是,您可以根据请求设置Apache HttpClient,这是我认为的正确设计。

我们已经在我们的项目中到处使用RequestConfig,我们现在无法承受重构,http客户端切换会随之而来。

现在我最终得到了一个RestTemplate池解决方案,我创建了一个名为RestTemplateManager的类,我完全负责创建模板并汇集它们。此管理器具有按服务和readTimeout分组的模板的本地缓存。想象一下具有以下结构的缓存哈希映射:

ServiceA | 1000 - > RestTemplate

ServiceA | 3000 - > RestTemplate

ServiceB | 1000 - > RestTemplate

密钥中的数字是readTimeout(以毫秒为单位)(密钥可以适应以后支持多于readTimeout)。因此,当ServiceA请求具有1000ms读取超时的模板时,管理器将返回缓存的实例,如果它不存在,则将创建并返回该实例。

在这种方法中,我从预先定义的RestTemplates中保存了自己,我只需要从上面的管理器请求RestTemplate。这也使初始化保持在最低限度。

这应该做,直到我有时间抛弃RestTemplate并使用更合适的解决方案。


1
投票

我自己刚刚遇到这个问题并且四处搜索没有提出任何我认为合适的解决方案。这是我的解决方案和思考过程。

您可以使用HttpComponentsClientHttpRequestFactory为RestTemplate设置超时。每次发出请求时,它都会在requestFactory上调用createRequest函数。它在这里是RequestConfig,它具有超时和一些请求特定的属性。然后在HttpContext上设置此RequestConfig。下面是尝试构建此RequestConfig和HttpContext的步骤(按顺序)

  1. 在HttpComponentsClientHttpRequestFactory中调用createHttpContext函数,该函数默认不执行任何操作并返回null。
  2. 如果它存在于HttpUriRequest中并将其添加到HttpContext,则获取RequestConfig。
  3. 在HttpComponentsClientHttpRequestFactory中调用createRequestConfig函数,该函数在内部从HttpClient获取RequestConfig,将其与requestFactory内部构建的RequestConfig合并,并将其添加到HttpContext。 (默认情况下会发生这种情况)

在我看来,所有这三个都可以围绕它们建立解决方案。我相信最简单,最强大的解决方案是围绕#1构建解决方案。我最终创建了自己的HttpComponentsRequestFactory并且刚刚覆盖了createHttpContext函数,该函数内部具有逻辑,以查看请求URI的路径是否与我为该pathPattern指定的超时提供的pathPattern匹配。

RestTemplate

然后,如果您愿意,可以使用默认超时创建此请求工厂的实例,并为此类特定路径指定自定义超时

public class PathTimeoutHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
  private List<PathPatternTimeoutConfig> pathPatternTimeoutConfigs = new ArrayList<>();

  protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
    for (PathPatternTimeoutConfig config : pathPatternTimeoutConfigs) {
      if (httpMethod.equals(config.getHttpMethod())) {
        final Matcher matcher = config.getPattern().matcher(uri.getPath());
        if (matcher.matches()) {
          HttpClientContext context = HttpClientContext.create();
          RequestConfig requestConfig = createRequestConfig(getHttpClient());  // Get default request config and modify timeouts as specified
          requestConfig = RequestConfig.copy(requestConfig)
              .setSocketTimeout(config.getReadTimeout())
              .setConnectTimeout(config.getConnectionTimeout())
              .setConnectionRequestTimeout(config.getConnectionRequestTimeout())
              .build();
          context.setAttribute(HttpClientContext.REQUEST_CONFIG, requestConfig);
          return context;
        }
      }
    }

    // Returning null allows HttpComponentsClientHttpRequestFactory to continue down normal path for populating the context
    return null;
  }

  public void addPathTimeout(HttpMethod httpMethod, String pathPattern, int connectionTimeout, int connectionRequestTimeout, int readTimeout) {
    Assert.hasText(pathPattern, "pathPattern must not be null, empty, or blank");
    final PathPatternTimeoutConfig pathPatternTimeoutConfig = new PathPatternTimeoutConfig(httpMethod, pathPattern, connectionTimeout, connectionRequestTimeout, readTimeout);
    pathPatternTimeoutConfigs.add(pathPatternTimeoutConfig);
  }

  private class PathPatternTimeoutConfig {
    private HttpMethod httpMethod;
    private String pathPattern;
    private int connectionTimeout;
    private int connectionRequestTimeout;
    private int readTimeout;
    private Pattern pattern;

    public PathPatternTimeoutConfig(HttpMethod httpMethod, String pathPattern, int connectionTimeout, int connectionRequestTimeout, int readTimeout) {
      this.httpMethod = httpMethod;
      this.pathPattern = pathPattern;
      this.connectionTimeout = connectionTimeout;
      this.connectionRequestTimeout = connectionRequestTimeout;
      this.readTimeout = readTimeout;
      this.pattern = Pattern.compile(pathPattern);
    }

    public HttpMethod getHttpMethod() {
      return httpMethod;
    }

    public String getPathPattern() {
      return pathPattern;
    }

    public int getConnectionTimeout() {
      return connectionTimeout;
    }

    public int getConnectionRequestTimeout() { return connectionRequestTimeout; }

    public int getReadTimeout() {
      return readTimeout;
    }

    public Pattern getPattern() {
      return pattern;
    }
  }
}

这种方法是高度可重用的,不需要为每个唯一的超时创建单独的RestTemplate,并且据我所知是线程安全。

© www.soinside.com 2019 - 2024. All rights reserved.