一、下降延迟:若是不采用链接池,每次链接发起Http请求的时候都会从新创建TCP链接(经历3次握手),用完就会关闭链接(4次挥手),若是采用链接池则减小了这部分时间损耗,别小看这几回握手,本人通过测试发现,基本上3倍的时间延迟java
二、支持更大的并发:若是不采用链接池,每次链接都会打开一个端口,在大并发的状况下系统的端口资源很快就会被用完,致使没法创建新的链接安全
BasicHttpClientConnectionManager
是个简单的链接管理器,它一次只能管理一个链接。尽管这个类是线程安全的,它在同一时间也只能被一个线程使用。BasicHttpClientConnectionManager
会尽可能重用旧的链接来发送后续的请求,而且使用相同的路由。若是后续请求的路由和旧链接中的路由不匹配,BasicHttpClientConnectionManager
就会关闭当前链接,使用请求中的路由从新创建链接。若是当前的链接正在被占用,会抛出java.lang.IllegalStateException
异常。bash
相对BasicHttpClientConnectionManager
来讲,PoolingHttpClientConnectionManager
是个更复杂的类,它管理着链接池,能够同时为不少线程提供http链接请求。Connections are pooled on a per route basis.当请求一个新的链接时,若是链接池有有可用的持久链接,链接管理器就会使用其中的一个,而不是再建立一个新的链接。服务器
PoolingHttpClientConnectionManager
维护的链接数在每一个路由基础和总数上都有限制。默认,每一个路由基础上的链接不超过2个,总链接数不能超过20。在实际应用中,这个限制可能会过小了,尤为是当服务器也使用Http协议时。多线程
下面的例子演示了若是调整链接池的参数:并发
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 将最大链接数增长到200
cm.setMaxTotal(200);
// 将每一个路由基础的链接增长到20
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大链接数增长到50
HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();复制代码
当一个HttpClient的实例不在使用,或者已经脱离它的做用范围,咱们须要关掉它的链接管理器,来关闭掉全部的链接,释放掉这些链接占用的系统资源。socket
CloseableHttpClient httpClient = <...>
httpClient.close();复制代码
经典阻塞I/O模型的一个主要缺点就是只有当组侧I/O时,socket才能对I/O事件作出反应。当链接被管理器收回后,这个链接仍然存活,可是却没法监控socket的状态,也没法对I/O事件作出反馈。若是链接被服务器端关闭了,客户端监测不到链接的状态变化(也就没法根据链接状态的变化,关闭本地的socket)。ide
HttpClient为了缓解这一问题形成的影响,会在使用某个链接前,监测这个链接是否已通过时,若是服务器端关闭了链接,那么链接就会失效。这种过期检查并非100%有效,而且会给每一个请求增长10到30毫秒额外开销。惟一一个可行的,且does not involve a one thread per socket model for idle connections的解决办法,是创建一个监控线程,来专门回收因为长时间不活动而被断定为失效的链接。这个监控线程能够周期性的调用ClientConnectionManager
类的closeExpiredConnections()
方法来关闭过时的链接,回收链接池中被关闭的链接。它也能够选择性的调用ClientConnectionManager
类的closeIdleConnections()
方法来关闭一段时间内不活动的链接。post
public static class IdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
// 关闭失效的链接
connMgr.closeExpiredConnections();
// 可选的, 关闭30秒内不活动的链接
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// terminate
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}复制代码
Http规范没有规定一个持久链接应该保持存活多久。有些Http服务器使用非标准的Keep-Alive
头消息和客户端进行交互,服务器端会保持数秒时间内保持链接。HttpClient也会利用这个头消息。若是服务器返回的响应中没有包含Keep-Alive
头消息,HttpClient会认为这个链接能够永远保持。然而,不少服务器都会在不通知客户端的状况下,关闭必定时间内不活动的链接,来节省服务器资源。在某些状况下默认的策略显得太乐观,咱们可能须要自定义链接存活策略。测试
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
// Honor 'keep-alive' header
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch(NumberFormatException ignore) {
}
}
}
HttpHost target = (HttpHost) context.getAttribute(
HttpClientContext.HTTP_TARGET_HOST);
if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
// Keep alive for 5 seconds only
return 5 * 1000;
} else {
// otherwise keep alive for 30 seconds
return 30 * 1000;
}
}
};
CloseableHttpClient client = HttpClients.custom()
.setKeepAliveStrategy(myStrategy)
.build();复制代码