記一次NoHttpResponseException問題排查

上傳文件程序會有必定的機率提示錯誤,錯誤率大概在1%如下,錯誤信息是:org.apache.http.NoHttpResponseException , s3-us-west-1.amazonaws.com:80 failed to respond,看着是上傳到S3的過程當中發送了網絡錯誤?apache

file

經過查閱資料,發現了一篇比較好的文章:一次NoHttpResponseException問題分析解決。這個文章的觀點是會發生這個錯誤的緣由是服務端關閉了鏈接,而客戶端還在使用該鏈接,致使服務端響應RST報文,客戶端收到RST報NoHttpResponseException異常。服務器

爲了說明這個場景,就要提一下Keepalive機制。Keepalive是HTTP的鏈接複用機制,在HTTP1.0時代,每一個請求通過三次握手後,只會傳輸一次HTTP請求和響應報文後,就進入四次揮手關閉鏈接了。而TCP創建鏈接和關閉鏈接的代價是比較大的,致使HTTP1.0的通道利用率較低,時延較高。針對這個問題,退出了Keepalive機制,一個TCP鏈接創建後,能夠在上面發送多個HTTP報文,只有這個TCP鏈接的空閒時間達到超時時間,纔會被關閉。HTTP1.1默認開啓Keepalive。這裏的關閉行爲可能發生在客戶端和服務端,好比客戶端的Keepalive超時時間更短,則客戶端就會先關閉鏈接,若是服務端配置的Keepalive超時時間更短,則服務端就會先關閉鏈接。網絡

乍看起來不管那一邊關閉鏈接都沒什麼問題,可是仍是有細節須要注意。好比服務端關閉鏈接,發送FIN包,在這個FIN包發送可是還未到達客戶端期間,客戶端若是繼續複用這個TCP鏈接,發送HTTP請求報文的話,服務端會由於在四次揮手期間不接收報文而發送RST報文給客戶端,客戶端收到RST報文就會提示異常。ui

根據上面的理論知識,能夠推測org.apache.http.NoHttpResponseException , s3-us-west-1.amazonaws.com:80 failed to respond這個錯誤發生的緣由是由於個人程序的HttpClient的Keepalive時間大於S3服務器的,致使S3服務端關閉鏈接時,可能發生異常。咱們作個試驗看看。url

首先寫一個簡單程序觀察一下AWS S3服務端的Keepalive時間spa

String url = "一個能夠訪問的S3下載地址";
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet(url);

httpClient.execute(request, response -> {
    String content = EntityUtils.toString(response.getEntity());
    System.out.println(content);
    return content;
});

Thread.sleep(99999);複製代碼

Wireshark抓包觀察HTTP響應報文後,通過多久進入四次揮手:.net

file

能夠看出服務端發送FIN包距離上一個請求的時間大概是23秒,也就是AWS S3服務端的Keepalive時間大體爲23秒。code

接着咱們模擬客戶端在服務端關閉鏈接的同時發送請求的場景,看看可否復現NoHttpResponseException錯誤:cdn

String url = "http://s3-us-west-1.amazonaws.com/sdpcs-prod-awsca/88ea9001-bad0-4b46-86e5-e6bc518c9fdc?Expires=1718171230&response-content-type=image/jpeg&response-cache-control=max-age%3D157680000&AWSAccessKeyId=AKIAI7P7PYLVYWVVYTLQ&Signature=iCeE6%2FIHtxmOarOc3Q1hUowWqDc%3D";
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet(url);

for (int i = 0; i < 100000; i++) {
    httpClient.execute(request, response -> {
        String content = EntityUtils.toString(response.getEntity());
        System.out.println(content);
        return content;
    });

    Thread.sleep(23000);
}複製代碼

多執行幾回,就能復現出NoHttpResponseException錯誤:server

六月 14, 2019 2:09:14 下午 org.apache.http.impl.execchain.RetryExec execute
信息: I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://s3-us-west-1.amazonaws.com:80: The target server failed to respond
六月 14, 2019 2:09:14 下午 org.apache.http.impl.execchain.RetryExec execute
信息: Retrying request to {}->http://s3-us-west-1.amazonaws.com:80複製代碼

分析抓包:

file

能夠看到2400號請求距離上一個請求23秒,而後在服務端還未收到2400號請求時,客戶端就收到了服務端發來的FIN請求,進入了四次揮手流程。而後當服務端收到2400號請求後,響應RST請求,致使客戶端提示錯誤。

HttpClient提供了關閉空閒鏈接的功能:

CloseableHttpClient httpClient = HttpClients.custom()
                .evictIdleConnections(5, TimeUnit.SECONDS)
                .build();複製代碼

咱們設置一個低於S3 Keepalive的時間再次執行,就不會出現NoHttpResponseException錯誤了。

除了在客戶端設置小於服務端的Keepalive時間,還有一種作法是在出現NoHttpResponseException時進行重試,也是能夠的,還能夠減小TIME_WAIT數量。

本文獨立博客地址:記一次NoHttpResponseException問題排查 | 木杉的博客

相關文章
相關標籤/搜索