掘金 后端 ( ) • 2024-04-27 09:53

《OkHttp源码:连接池与响应缓存》一文中,我们大体了解了执行一次请求时,如何从连接池中获取一个可用连接。现在,我们再来看看连接池的更多细节。

选用连接

在3.0版本及之后,RealConnection中有如下属性: image.png RealConnectionPool#transmitterAcquirePooledConnection方法,从连接池中获取一个可用连接。

  boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter, List<Route> routes, boolean requireMultiplexed) {
    // 加锁
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (requireMultiplexed && !connection.isMultiplexed()) continue;
      // 连接是否可用
      if (!connection.isEligible(address, routes)) continue;
      // 将transmitter添加到RealConnectionPool.transmitters中
      transmitter.acquireConnectionNoEvents(connection);
      return true;
    }
    return false;
  }

执行时每个请求时,都会为它创建一个Transmitter实例,这个类是Request与Connection之间的中间层。 image.png RealConnection#isEligible中,首先判断与Connection关联的transmitter数是否大于1,如果是则说明该连接正在被某个请求使用中,不能被选中而并发使用。 image.png

关闭连接

当我们创建OkHttpClient时,默认会创建一个maxIdleConnections=5、keepAliveDurationNs=5分钟的连接池。 image.png 当设置maxIdleConnections=0,或连接的noNewExchanges=true(被标记为空闲且不再接受请求),将被立即从连接池中移除。 image.png

关闭一个连接

RealConnectionPool有属性idleAtNanos,是该连接变为空闲时的时间戳,将用于计算该连接已空闲了多久。 image.png 那idleAtNanos何时被赋值呢?在Transmitter#releaseConnectionNoEvents方法中,当RealConnection的List<Reference>变为empty时,令idleAtNanos=System.nanoTime()。

Socket releaseConnectionNoEvents() {
	// 加锁
	assert (Thread.holdsLock(connectionPool));

	// 找出当前transmitter在connection.transmitters中的索引
	int index = -1;
	for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
	  Reference<Transmitter> reference = this.connection.transmitters.get(i);
	  if (reference.get() == this) {
		index = i;
		break;
	  }
	}

	if (index == -1) throw new IllegalStateException();

	// 移除当前transmitter
	RealConnection released = this.connection;
	released.transmitters.remove(index);
	this.connection = null;

	// 如果连接变得空闲,则记录idleAtNanos
	if (released.transmitters.isEmpty()) {
	  // 设置为当前时间
	  released.idleAtNanos = System.nanoTime();
	  if (connectionPool.connectionBecameIdle(released)) {
		return released.socket();
	  }
	}

	return null;
}

可见,RealConnection一直在ConnectionPool中,一次请求后,只是从transmitters列表中移除本次请求的Transmitter,同时如果连接变的空闲,则记录idleAtNanos。

定时清理

RealConnectionPool中,有用于执行cleanup任务的线程池。虽然maximumPoolSize相当于限制,但是某时刻最多允许一个线程在运行。 image.png

  // 标记cleanup任务是否在运行中,防止并发。
  boolean cleanupRunning;

  private final Runnable cleanupRunnable = () -> {
     while (true) {
      // 返回到下次运行的等待时间
      long waitNanos = cleanup(System.nanoTime());
      if (waitNanos == -1) return;
      if (waitNanos > 0) {
        long waitMillis = waitNanos / 1000000L;
        waitNanos -= (waitMillis * 1000000L);
        synchronized (RealConnectionPool.this) {
          try {
            // wait,到期或被唤醒后,继续循环  
            RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
          } catch (InterruptedException ignored) {
          }
        }
      }
    }
  };

cleanup逻辑如下:遍历连接池,找出空闲时间最长的那个连接a,以及空闲连接数idleConnectionCount;

  • 首先,a连接的idleAtNanos > keepAliveDurationNs,或者idleConnectionCount > maxIdleConnections,移除该连接,返回0;
  • 其次,空闲连接数没超过限制,且a连接空闲时间也未超限,说明没有可清理的连接,则返回a连接剩余存活时间;
  • 其次,所有线程都在使用中,则返回keepAliveDurationNs;
  • 最后,说明池中没有连接了,返回-1,cleanup线程将退出循环。

因此可知,执行一次cleanup最多清理一个连接。且清理线程最多只有一个,不是无脑循环,有wait也有退出

立即关闭所有空闲连接

当你需要关闭OkHttpClient时,可以调用RealConnectionPool#evictAll立即关闭空闲连接,而不受maxIdle、keepAliveDuration限制。 image.png 发现在spring-web包中OkHttp3ClientHttpRequestFactory#destroy中,有调用evictAll方法。 image.png