记一次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问题排查 | 木杉的博客

相关文章
相关标签/搜索