写这边文章时,背景如下
1.为了分析之前的一个tcp close wait问题
2.很多项目对httpclient的参数和使用都理解有问题,往往随便写一个或者使用网上的代码。导致在一些场景的tcp问题,为了规范化和正确的使用httpclient
大致有这些版本
- httpclient3.x
- httpclient4.x到httpclient4.3以下
- httpclient4.3以上
一般会选择4.3以上
https://www.baeldung.com/httpclient-connection-management 这是国外一个教学网站的说明,使用和说明还算比较ok
public class HttpClientUtils {
private static HttpClientContext context = HttpClientContext.create();
private static CloseableHttpClient httpClient;
static {
// 基本配置
RequestConfig requestConfig = RequestConfig.custom()
// 建连超时时间
.setConnectTimeout(5000)
// 传输超时时间
.setSocketTimeout(3000)
// 从连接池获取连接超时时间
.setConnectionRequestTimeout(10000)
.build();
// 连接管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 最大连接数
cm.setMaxTotal(80);
// 同一个路由最大连接数
cm.setDefaultMaxPerRoute(20);
// 客户端构造器
HttpClientBuilder clientBuilder = HttpClients
.custom()
.setConnectionManager(cm)
.setConnectionManagerShared(false)// 影响连接的关闭
.setDefaultRequestConfig(requestConfig);
httpClient = clientBuilder.build();
}
public static byte[] get(String url, String userAgent) throws IOException {
HttpGet httpGet = new HttpGet(url);
if (userAgent != null) {
httpGet.setHeader(HTTP.USER_AGENT, userAgent);
}
CloseableHttpResponse response = httpClient.execute(httpGet, context);
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
return IOUtils.toByteArray(entity.getContent());
} finally {
// 只需在传输完毕关闭流即可,详细见源码
EntityUtils.consumeQuietly(entity);
}
}
return null;
}
}
连接池的概念很重要。没有池子,每一次都要tcp建连断连,开销比较大。
以上的代码可以最简单的使用httpclient pool,主要关注以下内容
1.timeout和poolsize的设置
2.资源关闭
1
2
3
4
5
6
7
8
9
10
|
for
(
int
i =
0
; i <
20
; i++) {
try
{
}
catch
(IOException e) {
}
}
CountDownLatch latch =
new
CountDownLatch(
1
);
|
客户端
服务器
符合预期
多线程请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for
(
int
i =
0
; i <
100
; i++) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
try
{
}
catch
(IOException e) {
}
}).start();
}
}
CountDownLatch latch =
new
CountDownLatch(
1
);
|
20个符合预期
刚好为了测试单线程的close情况,于是把thread去掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for
(
int
i =
0
; i <
100
; i++) {
// new Thread(new Runnable() {
// @Override
// public void run() {
try
{
}
catch
(IOException e) {
}
// }
// }).start();
}
CountDownLatch latch =
new
CountDownLatch(
1
);
|
连接呢???
经过测试,与循环100这个数有关系于是模拟以下测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
for
(
int
i =
0
; i <
101
; i++) {
if
(i ==
98
|| i ==
99
|| i ==
100
) {
}
try
{
}
catch
(IOException e) {
}
}
CountDownLatch latch =
new
CountDownLatch(
1
);
|
打断点跟踪发现,reusable这个参数在100的时候,行为是和99和101不同的
于是看源码跟踪这个参数修改的地方
private volatile boolean reusable;
代码中有多处将此参数设置为false,其中在100的时候,命中断点。在MainClientExec.java中有如下使用,那么 reuseStrategy.keepAlive(response, context)是关键
继续看 reuseStrategy.keepAlive(response, context),有个叫Porxy-Connection的当100的时候,值为Close状态,证明这个是服务端的一些设置导致的,于是谷歌关键字keepalive
找到tomcat有如下参数配置,nginx也有,大致意思就是一个连接能被重复使用的次数,当超过他,就会断开。这也可以解释为什么100的时候,连接没了。101又开始建立新的连接
maxKeepAliveRequests | The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100. |