出现场景
项目使用微服务,将每个数据源拆分成了一个服务,并通过Eureka注册,web服务通过配置的不同数据源的url调用各个数据源的服务从而获取相应数据。
但近日部署后在跑全量更新缓存的过程中,发现了一个严重问题。缓存更新不完整,通过日志信息定位到,每次在调用MongoDB数据源微服务时,会发生无响应,导致更新任务无法继续进行下去,耗费大量时间。
而调用各个服务的接口正是使用RestTemplate实现的,但经过查看,RestTemplate的Bean并未进行超时配置。这就很方了,RestTemplate本身在不配置超时的时候,是不存在超时机制的,只能通过tcp协议的响应超时来结束连接,那要等多久?等不了的,系统等不了,我更等不了。
解决方案
嗯,反正问题是很好解决的,如下:
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectionRequestTimeout(10*1000);
httpRequestFactory.setConnectTimeout(10*1000);
httpRequestFactory.setReadTimeout(10*1000);
return new RestTemplate(httpRequestFactory);
完事儿,在加载RestTemplate的bean的过程中,注入超时,具体的超时时间,是根据normal情况下系统跑出来的耗时时长进行的设置。
分析
借此机会,准备好好分析下里面的配置项,从而达到,定义出一个合理的RestTemplate的Bean。
- setConnectTimeout(int timeout)方法
设置基础的HttpClient连接超时。
public void setConnectTimeout(int timeout) {
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
this.requestConfig = requestConfigBuilder().setConnectTimeout(timeout).build();
setLegacyConnectionTimeout(getHttpClient(), timeout);
}
- setBufferRequestBody(boolean bufferRequestBody)
该设置的默认值是ture,含义是使用内部缓存将请求的Body体进行缓冲处理。这里呢,如果通过POST或PUT发送大量数据时,建议将此属性更改为false,以免内存不足。
public void setBufferRequestBody(boolean bufferRequestBody) {
this.bufferRequestBody = bufferRequestBody;
}
- setConnectionRequestTimeout(int connectionRequestTimeout)
设置使用基础HttpClient从连接管理器请求连接时使用的超时(以毫秒为单位)。可以看到,这里的超时时间是允许为0的,那么问题来了,如果为零会发生什么事情:嗯没错,就是无限超时。
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
this.requestConfig = requestConfigBuilder().setConnectionRequestTimeout(connectionRequestTimeout).build();
}
- setHttpClient(HttpClient httpClient)
哎哟,看到这个方法,我有一个大胆的想法。。。可以自实现一个自定义的HttpClient了。嗯嗯不错
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
- setReadTimeout(int timeout)
该超时设置的是Socket读取超时时间。
public void setReadTimeout(int timeout) {
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build();
setLegacySocketTimeout(getHttpClient(), timeout);
}
- setLegacyConnectionTimeout
将指定的连接超时应用于已弃用的HttpClient实现。
private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
}
}
- setLegacySocketTimeout
将指定的Socket超时应用于已弃用的HttpClient实现。
private void setLegacySocketTimeout(HttpClient client, int timeout) {
if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
}
}
注意:这里的已弃用的HttpClient的实现主要是指HttpClient 4.3版本之前的实现。