最近在开发中使用http连接池调用的时候,线上报NoHttpResponseException(具体描述如下),由此展开研究下http连接池参数的具体设置
1:问题描述
java.lang.RuntimeException: org.apache.http.NoHttpResponseException: api.mch.weixin.qq.com:443 failed to respond
java.lang.RuntimeException: org.apache.http.NoHttpResponseException: api.mch.weixin.qq.com:443 failed to respond
2:版本依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.1</version>
</dependency>
3: SpringBean注入配置
1)httpclient 使用CloseableHttpClient实现类,通过工厂方法模式注入bean。
<bean id="httpClient" class="org.apache.http.impl.client.CloseableHttpClient"
factory-bean="httpClientBuilder" factory-method="build"/>
2)分别配置 connManager, keepAliveStrategy, retryHandler, defaultRequestConfig 属性。
<bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder">
<property name="connectionManager" ref="httpClientConnectionManager"></property>
<property name="keepAliveStrategy" ref="connectionKeepAliveStrategy"/>
<property name="retryHandler" ref="retryHandler"/>
<property name="defaultRequestConfig" ref="requestConfig"/>
</bean>
当然该builder还有其他属性需要设置,不过其中有好多都是connectionManager中的属性,源码设置如下,是在connectionManager这个属性没有配置的情况下,才会去利用builder下的属性配置去创建新的connectionManager,比如maxTotal,maxConnPerRoute,假如既配置了connectionManager,又配置了maxTotal等connectionManager的属性,配置的maxTotal是不生效的,只有connectionManager为null,没有配置的时候才生效。
3)connManager配置
<bean id="httpClientConnectionManager"
class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager"
destroy-method="shutdown">
<!--最大连接数-->
<property name="maxTotal" value="300"/>
<!--最大路由连接,每个请求地址的连接数-->
<property name="defaultMaxPerRoute" value="200"/>
<!--socketConfig配置-->
<property name="defaultSocketConfig" ref="socketConfig"/>
<!--检测不活跃连接的有效性时间-->
<property name="validateAfterInactivity" value="5000"/>
<!--连接存活时间-->
<constructor-arg index="0" value="5"/>
<constructor-arg index="1" value="SECONDS"/>
</bean>
-
connManager socket 配置
<bean id="socketConfig" class="org.apache.http.config.SocketConfig" factory-bean="socketConfigBuilder" factory-method="build"/>
<bean id = "socketConfigBuilder" class="org.apache.http.config.SocketConfig.Builder">
<!--超时时间-->
<property name="soTimeout" value="2000"/>
</bean>
4)connectionKeepAliveStrategy
<bean id="connectionKeepAliveStrategy"
class="com.seven.common.lib.util.HttpClient.BusinessCashierConnectionKeepAliveStrategy"/>
设置连接空闲可被重用的时间,空闲超过这个时间将不会reuse
这个KeepAliveStrategy配置可以配置DefaultConnectionKeepAliveStrategy默认的策略
默认策略如上,首先看服务端设置的连接超时间是多少,若存在与服务端保持一致,若不存在则设置为-1,一直保持连接。
配置了自己重写的方法如下 ,将最后返回的-1,重置了5s。空闲超过5s将不会被重用
5)retryHandler 配置
<bean id="retryHandler" class="org.apache.http.impl.client.DefaultHttpRequestRetryHandler">
<!--可重试的次数-->
<constructor-arg index="0" value="3"/>
<!--设置是否可重试-->
<constructor-arg index="1" value="true"/>
</bean>
6)defaultRequestConfig
<bean id="requestConfigBuilder" class="org.apache.http.client.config.RequestConfig.Builder">
<!--建立连接超时时间-->
<property name="connectTimeout" value="2000"/>
<!--从连接池拿连接超时时间-->
<property name="connectionRequestTimeout" value="1000"/>
<!--socket 请求时间-->
<property name="socketTimeout" value="2000"/>
</bean>
<bean id="requestConfig" class="org.apache.http.client.config.RequestConfig"
factory-bean="requestConfigBuilder" factory-method="build"/>
7)IdleConnectionEvictor 连接擦除配置
<bean class="org.apache.http.impl.client.IdleConnectionEvictor" init-method="start" destroy-method="shutdown">
<!--设置连接管理者-->
<constructor-arg index="0" ref="httpClientConnectionManager"/>
<!--空闲连接擦除时间-->
<constructor-arg index="1" value="3"/>
<constructor-arg index="2" value="SECONDS"/>
</bean>
这个注意下init-method这个配置,
当源码看当evictExpiredConnections或者evictIdleConnections为true,擦除空闲连接的线程才会启动,否则不会擦除空闲连接,由于不能直接通过属性注入,所以直接在初始化的时候通过init-method显式打开。
4:代码配置实现
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(socketTimeout)
.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(connectRequestTimeout)
.build();
ConnectionKeepAliveStrategy connectionKeepAliveStrategy = new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse httpResponse,
HttpContext httpContext) {
final HeaderElementIterator it = new BasicHeaderElementIterator(
httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
final HeaderElement he = it.nextElement();
final String param = he.getName();
final String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
logger.info("HeaderElement:{},param:{},value:{}", he, param, value);
return Long.parseLong(value) * 1000;
} catch (final NumberFormatException ignore) {
}
}
}
if (keepAliveDurationTime != 0) {
return keepAliveDurationTime;
}
return 5 * 1000l;
}
};
client = HttpClients.custom().setMaxConnPerRoute(maxPerRoute)
.setMaxConnTotal(maxConnectTotal).setDefaultRequestConfig(requestConfig)
.setConnectionTimeToLive(connectionTimeToLive, TimeUnit.SECONDS).evictIdleConnections((long) evictIdleConnections, TimeUnit.SECONDS)
.setKeepAliveStrategy(connectionKeepAliveStrategy).setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();