keepalive_requests 10000;
keepalive_timeout 30s;
在生产环境中,偶有同学反应会有HttpClient报NoHttpResponse的问题 org.apache.http.NoHttpResponseException: The target server failed to respond
遇到问题,首先打开Debug日志,我们可以看到这是因为尝试读数据是没读到,所以链接失败了
查阅TCP资料,我们可以知道一般读到end of stream都是因为服务端主动关闭连接造成的,此时客户端应该主动关闭连接,而不是继续复用。
通过WireShark抓包,我们可以看到的确是服务端已经发送了FIN,而客户端依旧使用了这个链接进行访问,所以就收到了RST,造成整个连接失败
那么为什么会继续复用已经关闭的链接呢?
我们继续从代码出发,通过日志,我们可以看出每次链接前都有次向连接池请求连接的过程
进入连接池管理代码,我们可以看到这边有两次检测是否要关闭:
首先是检测连接有没有过期,如果过期了就close
其次检测有没有配置validateAfterInactivity(默认为2000ms),如果超过了指定的时间间隔就进行validate
validate就是尝试读一次(超时1ms)来判断有没有失效
如果读到-1(end of stream)或者发生异常了就认为失效了
如果超时了就认为还是正常(因为httpclient阻塞读)
由上可见,要想根本解决问题,最好的方法就是客户端可以检测到服务已经过期了,主动关闭。
那么如何设置呢?追踪代码我们可以看到是client是从HttpHeader中读取Keep-Alive: timeout=xx,如果没有读到就认为永不超时
而Nginx配置的第二个参数就是这个,因此只要服务端将keepalive_timeout 30s;改为keepalive_timeout 30s 29s;即可,这里少1s为了防止网络延时造成的并发问题。
客户端方案
如果服务端不方便修改,那么从客户端这边修改则相对比较麻烦。
首先保证用最新的httpClient
其次有三种方法:
Retry。HttpClient默认是打开自动重试的,不要调用disableAutomaticRetries即可
对于jest这类有定时closeIdleConnection的,可以将调度时间调整为keep alive时间的一半以下以保证超时前回收
根据keep Alive时间,调整validateAfterInactivity小于keepAlive Time,但这种方法依旧不能避免同时关闭
Nginx的keepalive_requests为一次长连接最多响应的次数,在最后一次时会发送Connection: Close的Response Header
HttpClient在接受到服务端的FIN后链接会变成CLOSE_WAIT,直到下一次请求才会从连接池种拿出,判断要不要响应FIN,因此如果一直没有下一个连接,将会有大量CLOSE_WAIT
如果读数据时,操作系统已经将链接关闭,则返回I/O error: Connection reset,如果还未关闭,则返回end of stream也就是NoHttpResponse
HttpClient NoHttpResponseException问题排查
TCP三次握手与四次挥手
几种TCP连接中出现RST的情况
HTTPClient 4.5.2
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
本文首发于: http://czjxy881.coding.me/