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 。