jdk1.8+tomcat8+httpclient4.5.2

主要现象:

项目偶发出现org.apache.http.NoHttpResponseException: The target server failed to respond异常

定位原因:

查阅资料,此异常属于长连接keep-Alive的一种异常现象。当服务端某连接闲置超过keep-Alive超时时间后,服务端会关闭连接,进行四次挥手。如果此时,客户端再次拿此连接来访问服务端就会报NoHttpResponseException错误。

解决过程:

既然已经知道错误导致的原因,就可对症下药。主要解决思路有两种:

方案一:延长务端keep-Alive超时时间,拿tomcat举例,可以配置Connector 元素中的keepAliveTimeout参数;

方案二:降低客户端的keep-Alive时间,在服务端关闭闲置连接前关闭客户端连接。

方案一只能优化问题,但是并不能解决问题。因为keep-Alive超时时间不能设置为-1(永久),如果设置一直保持连接会极大的影响到服务端性能。

下面主要说一下方案二的解决方案,以httpClient4.5.2版本为例:

先贴最终的代码:

SSLContext sslcontext = SslUtils.createIgnoreVerifySSL();

主要配置参数说明:

  1. org.apache.http.impl.conn.PoolingHttpClientConnectionManager#setValidateAfterInactivity
  2. org.apache.http.impl.client.HttpClientBuilder#setConnectionManagerShared
  3. org.apache.http.impl.client.HttpClientBuilder#evictIdleConnections(long, java.util.concurrent.TimeUnit)
  4. org.apache.http.impl.client.HttpClientBuilder#setKeepAliveStrategy
  5. org.apache.http.impl.client.HttpClientBuilder#disableAutomaticRetries

ORG.APACHE.HTTP.IMPL.CONN.POOLINGHTTPCLIENTCONNECTIONMANAGER#SETVALIDATEAFTERINACTIVITY

从连接池中获取到空闲连接后,在使用之前校验空闲时间是否超过指定的时间,单位毫秒;注意,如果你像楼主一样,使用了

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

这块代码,那么请注意此处会把时间设置为2000ms.(ps:楼主在本地环境一直复现不了NoHttpResponseException的罪魁祸首)

【转载】 一次生产环境的NOHTTPRESPONSEEXCEPTION异常的排查记录_sed 方法路径:

org.apache.http.impl.conn.PoolingHttpClientConnectionManager#PoolingHttpClientConnectionManager(org.apache.http.conn.HttpClientConnectionOperator, org.apache.http.conn.HttpConnectionFactory<org.apache.http.conn.routing.HttpRoute,org.apache.http.conn.ManagedHttpClientConnection>, long, java.util.concurrent.TimeUnit)

逻辑上只要配置此处,即可保证连接在超时后关闭并重新从池子中获取(如果还超时,继续关闭连接并重新拿),无论哪一种配置,一定要配置此处,否则都会安装默认2秒过期时间来回收连接。感兴趣的可以看下源码:

private E getPoolEntryBlocking(

方法路径:

org.apache.http.pool.AbstractConnPool#getPoolEntryBlocking

ORG.APACHE.HTTP.IMPL.CLIENT.HTTPCLIENTBUILDER#SETCONNECTIONMANAGERSHARED和ORG.APACHE.HTTP.IMPL.CLIENT.HTTPCLIENTBUILDER#EVICTIDLECONNECTIONS(LONG, JAVA.UTIL.CONCURRENT.TIMEUNIT)

启动异步定时线程,关闭回收指定超时时间的空闲连接。如果在获取空闲连接前已经回收就没问题了,但是极端情况下也会出现NoHttpResponseException问题。比如:keep-Alive超时时间是20秒,然后定时配置15秒,假设第一次使用连接并释放时间为x,定时上次结束时间为y,y+15<x+20,也就是定时下次处理时,连接空闲时间还没有超过20秒,那么此处定时不会回收此连接,但是如果5秒后获取这个连接使用,肯定会报NoHttpResponseException异常。

evictIdleConnections需要配合setConnectionManagerShared=false使用,ConnectionManagerShared默认false。关键代码如下:

if (!this.connManagerShared) {

方法路径:

org.apache.http.impl.client.IdleConnectionEvictor#IdleConnectionEvictor(org.apache.http.conn.HttpClientConnectionManager, java.util.concurrent.ThreadFactory, long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit)

ORG.APACHE.HTTP.IMPL.CLIENT.HTTPCLIENTBUILDER#SETKEEPALIVESTRATEGY

此方法是设置客户端连接池维护的连接的keep-Alive时间。如果连接空闲时间超过设置的时间,则会关闭此连接并重新获取。主要相关源码如下:在初始化线程池时设置了过期时间expiry是创建时间+keep-Alive时间,已经过期时间在updateExpiry(连接池回收会调接方法)中被修改成最新时间。

//方法路径:org.apache.http.pool.PoolEntry#PoolEntry(java.lang.String, T, C, long, java.util.concurrent.TimeUnit) 此处的timeToLive 就是设置的keep-Alive时间

以上配置不是互斥也不少都需要配置,楼主亲自验证发现,只配置setValidateAfterInactivity或只配置setKeepAliveStrategy都可以。evictIdleConnections极端情况会有问题。

ORG.APACHE.HTTP.IMPL.CLIENT.HTTPCLIENTBUILDER#DISABLEAUTOMATICRETRIES

随便一提,httpclient默认会重试3次。如果接口不支持幂等,请注意不要使用重试。

OK,为了解决个问题,把源码看了一遍,特写博客以备以后注意使用。