目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口。最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB->服务端处理请求,查询数据并返回),发现原本的HttpClient连接池中的一些参数配置可能存在问题,如defaultMaxPerRoute、一些timeout时间的设置等,虽不能确定是由于此连接池导致接口查询慢,但确实存在可优化的地方,故花时间做一些研究。本文主要涉及HttpClient连接池、请求的参数配置,使用及源码解读。
以下是本文的目录大纲:
一、HttpClient连接池、请求参数含义
二、执行原理及源码解读
1、创建HttpClient,执行request
2、连接池管理
2.1、连接池结构
2.2、分配连接 & 建立连接
2.3、回收连接 & 保持连接
2.4、instream.close()、response.close()、httpclient.close()的区别
2.5、过期和空闲连接清理
三、如何设置合理的参数
一、HttpClient连接池、请求参数含义
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import javax.net.ssl.SSLException;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
public class HttpClientParamTest {
public static void main(String[] args) {
/**
* 创建连接管理器,并设置相关参数
*/
//连接管理器,使用无惨构造
PoolingHttpClientConnectionManager connManager
= new PoolingHttpClientConnectionManager();
/**
* 连接数相关设置
*/
//最大连接数
connManager.setMaxTotal(200);
//默认的每个路由的最大连接数
connManager.setDefaultMaxPerRoute(100);
//设置到某个路由的最大连接数,会覆盖defaultMaxPerRoute
connManager.setMaxPerRoute(new HttpRoute(new HttpHost("somehost", 80)), 150);
/**
* socket配置(默认配置 和 某个host的配置)
*/
SocketConfig socketConfig = SocketConfig.custom()
.setTcpNoDelay(true) //是否立即发送数据,设置为true会关闭Socket缓冲,默认为false
.setSoReuseAddress(true) //是否可以在一个进程关闭Socket后,即使它还没有释放端口,其它进程还可以立即重用端口
.setSoTimeout(500) //接收数据的等待超时时间,单位ms
.setSoLinger(60) //关闭Socket时,要么发送完所有数据,要么等待60s后,就关闭连接,此时socket.close()是阻塞的
.setSoKeepAlive(true) //开启监视TCP连接是否有效
.build();
connManager.setDefaultSocketConfig(socketConfig);
connManager.setSocketConfig(new HttpHost("somehost", 80), socketConfig);
/**
* HTTP connection相关配置(默认配置 和 某个host的配置)
* 一般不修改HTTP connection相关配置,故不设置
*/
//消息约束
MessageConstraints messageConstraints = MessageConstraints.custom()
.setMaxHeaderCount(200)
.setMaxLineLength(2000)
.build();
//Http connection相关配置
ConnectionConfig connectionConfig = ConnectionConfig.custom()
.setMalformedInputAction(CodingErrorAction.IGNORE)
.setUnmappableInputAction(CodingErrorAction.IGNORE)
.setCharset(Consts.UTF_8)
.setMessageConstraints(messageConstraints)
.build();
//一般不修改HTTP connection相关配置,故不设置
//connManager.setDefaultConnectionConfig(connectionConfig);
//connManager.setConnectionConfig(new HttpHost("somehost", 80), ConnectionConfig.DEFAULT);
/**
* request请求相关配置
*/
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(2 * 1000) //连接超时时间
.setSocketTimeout(2 * 1000) //读超时时间(等待数据超时时间)
.setConnectionRequestTimeout(500) //从池中获取连接超时时间
.setStaleConnectionCheckEnabled(true)//检查是否为陈旧的连接,默认为true,类似testOnBorrow
.build();
/**
* 重试处理
* 默认是重试3次
*/
//禁用重试(参数:retryCount、requestSentRetryEnabled)
HttpRequestRetryHandler requestRetryHandler = new DefaultHttpRequestRetryHandler(0, false);
//自定义重试策略
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
//Do not retry if over max retry count
if (executionCount >= 3) {
return false;
}
//Timeout
if (exception instanceof InterruptedIOException) {
return false;
}
//Unknown host
if (exception instanceof UnknownHostException) {
return false;
}
//Connection refused
if (exception instanceof ConnectTimeoutException) {
return false;
}
//SSL handshake exception
if (exception instanceof SSLException) {
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
//Retry if the request is considered idempotent
//如果请求类型不是HttpEntityEnclosingRequest,被认为是幂等的,那么就重试
//HttpEntityEnclosingRequest指的是有请求体的request,比HttpRequest多一个Entity属性
//而常用的GET请求是没有请求体的,POST、PUT都是有请求体的
//Res