一 排查過程
使用的httpclient客戶端,版本是:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
在生產環境發現有時會報錯:
org.apache.http.NoHttpResponseException: The target server failed to respond
懷疑是Nginx超時之後連接不可用,但是連接池裏面沒有剔除導致,爲了簡化排錯,在本地直接設置了一個線程循環跑,把sleep時間調整成Nginx超時時間5s,代碼如下:
DefaultHttpClient httpClient = new DefaultHttpClient();
httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
HttpGet httpGet = new HttpGet("http://192.168.31.176/index_10s.html");
httpGet.addHeader("User-Agent", USER_AGENT);
CloseableHttpResponse httpResponse = null;
for (int i = 0; i < 100; i++)
try {
httpResponse = httpClient.execute(httpGet);
System.out.println("GET Response Status:: "
+ httpResponse.getStatusLine().getStatusCode());
BufferedReader reader = null;
reader = new BufferedReader(new InputStreamReader(
httpResponse.getEntity().getContent()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = reader.readLine()) != null) {
response.append(inputLine);
}
Thread.sleep(5000);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
這裏把httpclient設置成了不重試的機制。在跑了之後,發現確實出現了NoHttpResponseException報錯。
把當時的包抓下來,看到如下所示:
- 首先是三次建聯過程;
- 中間兩個get都成功,注意相差了5s,這是Nginx的keepalive時間;
- 下面是關鍵過程:
我們看到,
(1)在首個包發送完成之後5s,已經到達Nginx的keepalive時間,此時服務端主動斷開連接,發送了FIN包(注意這裏的客戶端服務端是指的現在192是客戶端(發起http請求的一方),服務端指的是176(Nginx))。
(2)客戶端響應服務端請求,發送ACK包,此時192處於CLOSE_WAIT狀態,176處於FIN_WAIT2狀態,此時192仍然可以向176發送數據,但是176不會再向192發送數據,但是仍人可以接收192的數據;
(3)這時候我們發現,192向176發送了get請求,這個請求自然就被rst了;
二 httpclient使用
通常我們在使用的時候,不會考慮是否需要設置retry,實際上設置retry爲false是非常重要的,在涉及到一致性的請求中,如果一次請求失敗之後重新嘗試,那中間會發生什麼問題是未知的(代碼返回失敗!=請求失敗)