在上一篇文章里我们介绍了 httpclient 连接池中连接的申请,在这里我们主要介绍连接的和释放。
http连接的释放
httpclient 连接池中连接对象的释放主要涉及了ConnectionHolder 对象实例的 releaseConnection() 方法,PoolingHttpClientConnectionManager 对象实例的 releaseConnection() 方法以及Cpool 对象实例的 release() 方法,核心代码如下:
/*ConnectionHolder*/
public void releaseConnection() {
releaseConnection(this.reusable);
}
private void releaseConnection(final boolean reusable) {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) {
if (reusable) {
this.manager.releaseConnection(this.managedConn,this.state, this.validDuration, this.timeUnit);
} else {
try {
this.managedConn.close();
log.debug("Connection discarded");
} catch (final IOException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
} finally {
this.manager.releaseConnection(this.managedConn, null, 0, TimeUnit.MILLISECONDS);
}
}
}
}
}
/*PoolingHttpClientConnectionManager*/
public void releaseConnection(final HttpClientConnection managedConn, final Object state, final long keepalive, final TimeUnit timeUnit) {
Args.notNull(managedConn, "Managed connection");
synchronized (managedConn) {
final CPoolEntry entry = CPoolProxy.detach(managedConn);
if (entry == null) {
return;
}
final ManagedHttpClientConnection conn = entry.getConnection();
try {
if (conn.isOpen()) {
final TimeUnit effectiveUnit = timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS;
entry.setState(state);
entry.updateExpiry(keepalive, effectiveUnit);
if (this.log.isDebugEnabled()) {
final String s;
if (keepalive > 0) {
s = "for " + (double) effectiveUnit.toMillis(keepalive) / 1000 + " seconds";
} else {
s = "indefinitely";
}
this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
}
conn.setSocketTimeout(0);
}
} finally {
this.pool.release(entry, conn.isOpen() && entry.isRouteComplete());
if (this.log.isDebugEnabled()) {
this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute()));
}
}
}
}
/*Cpool*/
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);
if (reusable && !this.isShutDown) {
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();
}
}
ConnectionHolder 对象根据是否重用连接做不同的处理,如果不重用连接那么调用 ManagedHttpClientConnection 的 close() 方法,本质和上一篇文章中 entry.close() 一样关闭 socket ,设置 bind 的 socket 为空。然后继续调用 PoolingHttpClientConnectionManager 对象的releaseConnection 方法。
在 PoolingHttpClientConnectionManager 对象的 releaseConnection 方法里判断连接是否打开 conn.isOpen() ,判断本质和上一篇文章中entry.isOpen() 一样。如果打开,延长 CpoolEntry 的过期时间为当前时间 + keeplive 时间,然后调用 Cpool 的 release() 方法。
对于 Cpool 的 release() 方法,首先从 global 连接池正在使用连接集合leased 中移除当前 CpoolEntry ,如果重用则加入 global 连接池可用连接集合 available 中。然后找到前面文章介绍的当前请求路由 route 与之对应的连接池 RouteSpecificPool ,在该 individual 连接池正在使用连接集合 leased 中移除当前 CpoolEntry ,如果重用则加入 individual 连接池可用连接集合 available 中。
最后从 individual 连接池的请求队列里取出一个 item ,如果不为空,则在对象锁上唤醒在上一篇文章中在对象锁上等待的所有线程,表示当前 route 已经有连接释放,可以继续去申请可用连接了,然后在 global 连接池的 pending 集合里移除这个 item 。
个人觉得在连接申请和释放的时候还有一定的优化空间,申请连接的时候,当连接池中不能申请到可用连接,会把当前线程在对象 condition 上等待,对象 condition 是 global 连接池 Cpool 的属性。释放连接的时候,归还连接到invidual route pool 和 global pool 之后,通过condition.signalAll()方法唤醒在 condition 对象上等待的所有线程。试想如下情况:
有 route domain-a.com 和 domain-b.com。
domain-a.com 的 individual 连接池和 domain-b.com 的 individual连接池均满。
这时有2个线程 thread-a 和 thread-b 分别向两个 route 申请连接,结果两个 thread 都在 Cpool 池的 condition 对象上等待。
这时如果 domain-a.com 以前的请求归还连接,那么会同时唤醒两线程,thread-a 是可以获得连接的,因为有连接归还。
thread-b 依旧不能获得连接而继续在 condition 对象上进入等待状态,因为没有连接归还,所以 thread-b 白白被唤醒了一次。
如果 condition 是 individual pool 级别的,那么就可以做到针对每个 route 单独的等待和唤醒请求连接的线程,从而避免上述 thread-b 唤醒之后依然得不到连接而再次进入等待状态。以上也是自己对此设计的想法,欢迎大家一起讨论学习。
目前先写到这里,在下一篇文章里我们介绍 http 连接的重用和 keep-alive 。