2.1 连接管理器HttpClientConnectionManager
首先介绍路由(HttpRoute)的概念,我们可以理解成一条连接的路线。如果多个线程访问的是同一个IP+端口,我们就认为他们实际上使用的是同一个HttpRoute。
HTTP连接不是一个线程安全的对象,因此一个HTTP连接一次只能由一个线程使用。HttpClient使用HttpClientConnectionManager接口来管理Http连接,它管理Http连接的生命周期和线程安全,保证一个连接至多有一个线程访问。
2.1.1* BasicHttpClientConnectionManager
一次只维护一个连接,因此它只能被一个线程使用。如果持久连接的路由与连接请求的路由不匹配,则会关闭现有连接并重新打开给定路由。如果连接已经被分配,则抛出java.lang.IllegalStateException。
可以理解成MaxTotal=1,DefaultMaxPerRoute=1的PoolingHttpClientConnectionManager。
2.1.2 PoolingHttpClientConnectionManager
通过名字就可以知道这是一个Http连接池。它将连接按照路由进行合并,如果一个请求的路由在池中存在并且有空闲的连接,就复用连接池中的连接而不是创建新的连接。
连接池有两个重要的参数,那就是连接池中连接上限MaxTotal和每一个路由的连接上限DefaultMaxPerRoute。默认参数的MaxTotal=20,DefaultMaxPerRoute=2。这个一般都不能够满足我们生产环境的需要,因此在使用连接池时有必要根据需要自己设置这两个参数。
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
2.1.3 连接池原理
2.1.3.1 Http连接池的结构
下面中的代码选自HttpClient的源码:
连接池的结构:
private final Map<T, RouteSpecificPool<T, C, E>> routeToPool; //每个route有一个自己的连接池
private final Set<E> leased; //已经使用的连接集合
private final LinkedList<E> available; //空闲连接集合
private final Map<T, Integer> maxPerRoute; //每个route的连接最大数
private volatile int defaultMaxPerRoute; //默认每个route的最大连接数
private volatile int maxTotal; //连接池连接上限
我们可以看到连接池结构有一个空闲连接池的集合和一个已使用连接池的集合,此外对每个route都有自己的一个连接池。
2.1.3.2 获取连接
从连接池获取连接的过程简单来说就是从available集合中拿出一个连接放入leased集合中。详细的过程见下面的代码,如果不愿意看代码可以直接看注释:
从连接池获取连接的过程
final RouteSpecificPool<T, C, E> pool = getPool(route);
E entry;
for (;;) {
Asserts.check(!this.isShutDown, "Connection pool shut down");
for (;;) { //遍历route的连接池,查找其中没有close掉的连接
entry = pool.getFree(state);
if (entry == null) {
break;
}
if (entry.isExpired(System.currentTimeMillis())) {
entry.close();
}
if (entry.isClosed()) { //关闭连接池中已经close的连接
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
if (entry != null) { //连接池中有可以复用的连接,对available和leased两个集合进行操作
this.available.remove(entry);
this.leased.add(entry);
onReuse(entry);
return entry;
}
//...
if (pool.getAllocatedCount() < maxPerRoute) { //route的连接池未满,创建新连接
final int totalUsed = this.leased.size();
final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
if (freeCapacity > 0) { //连接池未超出上限
final int totalAvailable = this.available.size();
if (totalAvailable > freeCapacity - 1) {
if (!this.available.isEmpty()) {
final E lastUsed = this.available.removeLast();
lastUsed.close();
final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute());
otherpool.remove(lastUsed);
}
}
final C conn = this.connFactory.create(route);
entry = pool.add(conn);
this.leased.add(entry);
return entry;
}
}
boolean success = false;
2.1.3.3 释放连接
和获取连接相对的,释放连接的过程简单来说就是从leased集合中拿出一个连接放入available集合中。具体逻辑见下面的代码:
释放连接
public void release(final E entry, final boolean reusable) {
this.lock.lock();
try {
if (this.leased.remove(entry)) {
final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
pool.free(entry, reusable); //free在route的连接池中将连接移动到available
if (reusable && !this.isShutDown) { //可复用,将连接移动到available
this.available.addFirst(entry);
} else {
entry.close(); //不可复用,关闭连接
}
onRelease(entry);
Future<E> future = pool.nextPending();
if (future != null) {
this.pending.remove(future);
} else {
future = this.pending.poll();
}
if (future != null) {
this.condition.signalAll();
}
}
} finally {
this.lock.unlock();
}
}