okhttp之RealConnectionPool连接回收机制解析

690 阅读3分钟
  1. 首先简单介绍okhttp是一个Http请求的库,以下引用官方文档的介绍:

OkHttp is an HTTP client that’s efficient by default:

  • HTTP/2 support allows all requests to the same host to share a socket.
  • Connection pooling reduces request latency (if HTTP/2 isn’t available).
  • Transparent GZIP shrinks download sizes.
  • Response caching avoids the network completely for repeat requests..
  1. 连接池切入点分析: 2.1 okhttp3.ConnectionPool :主要充当维护连接管理者,底层实现是依附于okhttp3.internal.connection.RealConnectionPool
/**
 * Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
 * share the same {@link Address} may share a {@link Connection}. This class implements the policy
 * of which connections to keep open for future use.
 */
public final class ConnectionPool {
  final RealConnectionPool delegate;

  /**
   * Create a new connection pool with tuning parameters appropriate for a single-user application.
   * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
   * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
   */
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
  }

  /** Returns the number of idle connections in the pool. */
  public int idleConnectionCount() {
    return delegate.idleConnectionCount();
  }

  /** Returns total number of connections in the pool. */
  public int connectionCount() {
    return delegate.connectionCount();
  }

  /** Close and remove all idle connections in the pool. */
  public void evictAll() {
    delegate.evictAll();
  }
}

2.2 RealConnectionPool:是真正管理维护连接的类,其中连接回收关键的方法:okhttp3.internal.connection.RealConnectionPool#cleanup 以及okhttp3.internal.connection.RealConnectionPool#pruneAndGetAllocationCount

RealConnectionPool源码.png

2.3 cleanup: 由okhttp3.internal.connection.RealConnectionPool#cleanupRunnable触发调用,遍历连接列表,尝试找到当前最长空闲时长的连接以及其对应时长,判断是否可以被回收。

/**
 * Performs maintenance on this pool, evicting the connection that has been idle the longest if
 * either it has exceeded the keep alive limit or the idle connections limit.
 *
 * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
 * -1 if no further cleanups are required.
 */
long cleanup(long now) {
  int inUseConnectionCount = 0;
  int idleConnectionCount = 0;//统计空闲连接数量
  RealConnection longestIdleConnection = null;
  long longestIdleDurationNs = Long.MIN_VALUE;

  // Find either a connection to evict, or the time that the next eviction is due.
  synchronized (this) {
    // 遍历查找最长空闲时间的连接以及该连接对应时长
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
      RealConnection connection = i.next();

      // If the connection is in use, keep searching.
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        inUseConnectionCount++;
        continue;
      }

      idleConnectionCount++;

      // If the connection is ready to be evicted, we're done.
      long idleDurationNs = now - connection.idleAtNanos;
      if (idleDurationNs > longestIdleDurationNs) {
        longestIdleDurationNs = idleDurationNs;
        longestIdleConnection = connection;
      }
    }

    if (longestIdleDurationNs >= this.keepAliveDurationNs
        || idleConnectionCount > this.maxIdleConnections) {
      // We've found a connection to evict. Remove it from the list, then close it below (outside
      // of the synchronized block).
      // 满足最大空闲连接个数>配置个数或者最长空闲时间的连接其空间时间超过存活配置时间,触发回收
      connections.remove(longestIdleConnection);
    } else if (idleConnectionCount > 0) {
      // A connection will be ready to evict soon.
      // 返回连接的到期时间
      return keepAliveDurationNs - longestIdleDurationNs;
    } else if (inUseConnectionCount > 0) {
      // All connections are in use. It'll be at least the keep alive duration 'til we run again.
      //  当前无空闲连接
      return keepAliveDurationNs;
    } else {
      // No connections, idle or in use.
      cleanupRunning = false;
      return -1;
    }
  }

  closeQuietly(longestIdleConnection.socket());

  // Cleanup again immediately.
  return 0;
}

2.4 pruneAndGetAllocationCount:connection.transmitters维护了引用,这里回收基于引用计数。

/**
 * Prunes any leaked transmitters and then returns the number of remaining live transmitters on
 * {@code connection}. Transmitters are leaked if the connection is tracking them but the
 * application code has abandoned them. Leak detection is imprecise and relies on garbage
 * collection.
 */
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
  List<Reference<Transmitter>> references = connection.transmitters;
  for (int i = 0; i < references.size(); ) {
    Reference<Transmitter> reference = references.get(i);

    if (reference.get() != null) {
      i++;
      continue;
    }

    // We've discovered a leaked transmitter. This is an application bug.
    TransmitterReference transmitterRef = (TransmitterReference) reference;
    String message = "A connection to " + connection.route().address().url()
        + " was leaked. Did you forget to close a response body?";
    Platform.get().logCloseableLeak(message, transmitterRef.callStackTrace);

    references.remove(i);
    // 指定该连接不再受理请求
    connection.noNewExchanges = true;

    // If this was the last allocation, the connection is eligible for immediate eviction.
    if (references.isEmpty()) {
      connection.idleAtNanos = now - keepAliveDurationNs;
      return 0;
    }
  }

  return references.size();
}

2.5 TransmitterReference

static final class TransmitterReference extends WeakReference<Transmitter> {
  /**
   * Captures the stack trace at the time the Call is executed or enqueued. This is helpful for
   * identifying the origin of connection leaks.
   */
  final Object callStackTrace;

  TransmitterReference(Transmitter referent, Object callStackTrace) {
    super(referent);
    this.callStackTrace = callStackTrace;
  }
}