美文网首页
记一次NoHttpResponseException问题排查

记一次NoHttpResponseException问题排查

作者: 木杉是谁 | 来源:发表于2020-02-08 09:41 被阅读0次

    上传文件程序会有一定的概率提示错误,错误率大概在1%以下,错误信息是:org.apache.http.NoHttpResponseException , s3-us-west-1.amazonaws.com:80 failed to respond,看着是上传到S3的过程中发送了网络错误?

    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报文就会提示异常。

    根据上面的理论知识,可以推测org.apache.http.NoHttpResponseException , s3-us-west-1.amazonaws.com:80 failed to respond这个错误发生的原因是因为我的程序的HttpClient的Keepalive时间大于S3服务器的,导致S3服务端关闭连接时,可能发生异常。我们做个试验看看。

    首先写一个简单程序观察一下AWS S3服务端的Keepalive时间

    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响应报文后,经过多久进入四次挥手:

    file

    可以看出服务端发送FIN包距离上一个请求的时间大概是23秒,也就是AWS S3服务端的Keepalive时间大致为23秒。

    接着我们模拟客户端在服务端关闭连接的同时发送请求的场景,看看能否复现NoHttpResponseException错误:

    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错误:

    六月 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问题排查 | 木杉的博客

    相关文章

      网友评论

          本文标题:记一次NoHttpResponseException问题排查

          本文链接:https://www.haomeiwen.com/subject/rugbxhtx.html