HttpComponents HttpClient连接池(3)-连接的释放
- 2020 年 4 月 1 日
- 笔记
在上一篇文章里我们介绍了 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 。