出現場景
項目使用微服務,將每個數據源拆分成了一個服務,並通過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版本之前的實現。