我有一个@Service
有几个方法,每个方法都使用不同的web api。每次调用都应具有自定义读取超时。
拥有一个RestTemplate实例并在每个方法中通过工厂更改超时是否是线程安全的
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory())
.setReadTimeout(customMillis);
我担心的是我更改了工厂的超时,而不是RequestConfig
。考虑到这些方法可能会被多个用户同时调用,这种方法是否会是线程安全的?或者每种方法都应该有自己的RestTemplate
?
答案 0 :(得分:2)
选项1:多个RestTemplate
如果要更改创建的连接的属性,则每个配置需要一个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.
}
}
然后您可以根据需要将它们连接到您的服务:
@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...
}
选项2:在断路器中包裹呼叫
如果您正在呼叫外部服务,则可能should be using a circuit breaker为此。 Spring Boot与Hystrix配合使用,Hystrix是断路器模式的流行实现。使用hystrix,您可以控制调出的每个服务的回退以及超时。
假设您有两个服务A选项:1)便宜但有时慢2)昂贵但快速。您可以使用Hystrix放弃Cheap / Slow并在需要时使用Expensive / Fast。或者你可以没有备份,只是让Hystrix调用一个提供合理默认值的方法。
未经测试的例子:
@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!"
}
}
后备方法也有选项。您可以使用@HystrixCommand
注释该方法并尝试其他服务调用。或者,您可以提供合理的默认值。
答案 1 :(得分:2)
我假设您希望在响应时间过长的情况下读取超时。
一种可能的解决方案是,如果请求在给定时间内没有完成,则通过取消请求来自行实现超时。
为实现这一目标,您可以改用<Value>
,它内置支持超时和取消等异步操作。
这使您可以更好地控制每个请求的超时,例如:
AsyncRestTemplate
答案 2 :(得分:2)
在初始化RestTemplate
之后更改工厂的超时只是一种等待发生的竞争条件(如Todd explained)。 RestTemplate
实际上是为预先配置的超时而构建的,并且这些超时在初始化后保持不变。如果您使用Apache HttpClient
,那么您可以为每个请求设置RequestConfig
,这是我认为的正确设计。
我们已经在项目的任何地方使用RestTemplate
,我们现在无法承受重构,http客户端切换会随之发生。
现在我最终得到了一个RestTemplate
池解决方案,我创建了一个名为RestTemplateManager的类,我全权负责创建模板并汇集它们。此管理器具有按服务和readTimeout分组的模板的本地缓存。想象一下具有以下结构的缓存哈希映射:
ServiceA | 1000 - &gt; RestTemplate
ServiceA | 3000 - &gt; RestTemplate
ServiceB | 1000 - &gt; RestTemplate
密钥中的数字是readTimeout(以毫秒为单位)(密钥可以适应以后支持多于readTimeout)。因此,当ServiceA请求具有1000ms读取超时的模板时,管理器将返回缓存的实例,如果它不存在则将创建并返回。
在这种方法中,我从预先定义的RestTemplates中保存了自己,我只需要从上面的管理器请求RestTemplate。这也使初始化保持在最低限度。
直到我有时间抛弃RestTemplate并使用更合适的解决方案。
答案 3 :(得分:1)
我自己遇到了这个问题,到处搜索并没有提出我认为效果很好的解决方案。这是我背后的解决方案和思考过程。
您可以使用HttpComponentsClientHttpRequestFactory为RestTemplate设置超时。每次发出请求时,内部都会在requestFactory上调用createRequest函数。在这里,具有超时的RequestConfig和一些请求特定的属性被设置。然后在HttpContext上设置此RequestConfig。以下是尝试构建此RequestConfig和HttpContext的步骤(按顺序)
我认为,所有这三种解决方案都可以围绕它们构建解决方案。我认为,最简单,最可靠的解决方案是围绕#1构建解决方案。我最终创建了自己的HttpComponentsRequestFactory,只是重写了createHttpContext函数,该函数在内部具有逻辑功能,以查看请求URI的路径是否与为该pathPattern提供的指定超时提供的pathPattern匹配。
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;
}
}
}
然后,您可以根据需要使用默认超时创建此请求工厂的实例,并为像这样的特定路径指定自定义超时
@Bean
public PathTimeoutHttpComponentsClientHttpRequestFactory requestFactory() {
final PathTimeoutHttpComponentsClientHttpRequestFactory factory = new PathTimeoutHttpComponentsClientHttpRequestFactory();
factory.addPathTimeout(HttpMethod.POST, "\\/api\\/groups\\/\\d+\\/users\\/\\d+", 1000, 1000, 30000); // 30 second read timeout instead of 5
factory.setConnectionRequestTimeout(1000);
factory.setConnectTimeout(1000);
factory.setReadTimeout(5000);
return factory;
}
@Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(requestFactory());
...
return restTemplate;
}
这种方法是高度可重用的,不需要为每个唯一的超时都创建单独的RestTemplate,并且据我所知是线程安全的。
答案 4 :(得分:0)
类似于@Todd的答案
我们可以考虑一下:RestTemplate一旦构建,就可以认为是线程安全的。 Is RestTemplate thread safe?
让我们缓存RestTemplates,就像工厂一样。
由于不同的方法需要不同的超时时间,因此我们可以在需要时延迟获取指定的rest模板。
class GlobalClass{
....
private static Map<Integer, RestTemplate> timeoutToTemplateMap =
new ConcurrentHashMap<>();
...
public static getRestTemplate(Integer readTimeout){
return timeoutToTemplateMap.computeIfAbsent(readTimeout,
key->Utility.createRestTemplate(key)
}
}
@Service
.....
serviceMethodA(Integer readTimeout){
GlobalClass.getRestTemplate(readTimeout).exchange()
}
....
@Utility
.....
static createRestTemplate(Integer timeout){
HttpComponentsClientHttpRequestFactory factory = getFactory()
factory.setReadTimeout(timeout);
return new RestTemplate(factory);
// rest template is thread safe once created as no public methods change
// the fields of the rest template
}
.....
这类似于Todd的方法,但是它将扩展到任何类型的读取超时,并将使用对象的缓存,可能是flyweight-cum-factory模式。如果我错了,请纠正我。