HttpComponents HttpClient连接池(3)-连接的释放

上一篇文章里我们介绍了 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 。


版权声明:本文为weixin_46073333原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。