在Java 11中,HttpClient被添加进去。支持Http/1.1 和Http/2,并分别支持同步和异步调用两种方法。
上周使用HttpClient, 对一个后端服务进行压测,遇到了一些问题。
场景描述
场景比较简单,就是开200个线程,对后端服务接口进行压测,服务接口调用使用Java HttpClient。
问题描述
从后端服务监控看,RPS远不及预期,从LB的监控看,有数4000+的并发连接。这和实际的情况不符啊。
我开了200个线程,HttpClient的调用采用同步方式调用,按道理使用连接池,应该是200个并发链接才对,这里面一定有问题。
日志分析
先看下客户端日志吧,有大量的日志如下,
应该是创建的连接过多,创建新的tcp连接失败,再使用netstat 看下tcp连接的情况,
很快就达到16000多个连接。这说明连接池肯定没有复用。
有两种可能,一种是用完的连接没有回收到连接池,一种是请求的时候没有从连接池中获取到可复用的连接。
代码分析
单步调试下,在
HttpConnection::getConnection()中,
if (!secure) {
c = pool.getConnection(false, addr, proxy);
if (c != null && c.checkOpen() /* may have been eof/closed when in the pool */) {
final HttpConnection conn = c;
if (DEBUG_LOGGER.on())
DEBUG_LOGGER.log(conn.getConnectionFlow()
+ ": plain connection retrieved from HTTP/1.1 pool");
return c;
} else {
return getPlainConnection(addr, proxy, request, client);
}
} else { // secure
if (version != HTTP_2) { // only HTTP/1.1 connections are in the pool
c = pool.getConnection(true, addr, proxy);
}
if (c != null && c.isOpen()) {
final HttpConnection conn = c;
if (DEBUG_LOGGER.on())
DEBUG_LOGGER.log(conn.getConnectionFlow()
+ ": SSL connection retrieved from HTTP/1.1 pool");
return c;
} else {
String[] alpn = null;
if (version == HTTP_2 && hasRequiredHTTP2TLSVersion(client)) {
alpn = new String[] { "h2", "http/1.1" };
}
return getSSLConnection(addr, proxy, alpn, request, client); // 跑到这了
}
}
跑到了最后,协议走的是http/2。 协议如果走的http/2的话,没有从连接池中获取可复用连接。
解决
那么,显示的指定下http协议版本1.1。
重新跑下,
可以看到连接数变成了200个,也不再报错了,并发请求量也提升了。
总结
- 一定要显示的声明协议版本,否则缺省是http/2(支持ssl情况下);
- 如果是http/2,不会使用连接池,导致创建过多的连接,有可能将客户端和服务器都打死。