OkHttp 的连接池(ConnectionPool)

103 阅读4分钟

一、怎么配置(以及默认值)

  • 默认:新建 OkHttpClient() 时会带一个默认连接池,最多保留 5 个空闲连接,空闲 5 分钟后逐出。这个默认值在官方文档/3.x Javadoc 里写得很清楚。
  • 自定义:通过 OkHttpClient.Builder.connectionPool(...) 传入。
val pool = ConnectionPool(
    /* maxIdleConnections = */ 10,
    /* keepAliveDuration = */ 5, TimeUnit.MINUTES
)
val client = OkHttpClient.Builder()
    .connectionPool(pool)
    .build()
  • 共享/复用:用 client.newBuilder() 派生出来的客户端共享同一个连接池与线程池,这是官方推荐的用法。

小提示:连接池只管“空闲连接的数量/保活时长”;总并发连接数不是靠连接池限制,而是由 Dispatcher 的并发(maxRequests / maxRequestsPerHost)间接决定。

二、工作原理(连接如何复用与回收)

  1. 按 Address 复用

    OkHttp 会把 URL 与 OkHttpClient 的静态配置(协议、端口、TLS/代理/ConnectionSpec 等)组合成一个 Address同一 Address 的请求尽量共用同一条底层 TCP 连接:

  • HTTP/1.x:一次只跑一对请求/响应;响应读完、ResponseBody 关闭后连接才回到池里可复用。

  • HTTP/2:同一连接多路复用,并行跑多个请求。

    这能显著降低握手与时延、提升吞吐并省电。

  1. 取用与建连流程

    发起请求时:先用 Address 去池里找可用连接→ 找不到再新建连接(包括 DNS、TLS、代理、以及可能的 Fast Fallback 路由重试)→ 请求完成后把连接放回池以备下次复用。空闲一段时间后会被逐出。

  2. 空闲管理策略

    连接进入空闲队列后,如果 空闲时长 > keepAliveDuration 或者空闲数量超过 maxIdleConnections,清理线程会逐出老的空闲连接(OkHttp 5 里由内部的 RealConnectionPool/TaskRunner 负责维护)。

  3. 监控与操作

    ConnectionPool 提供一些可观测方法:connectionCount()(池内连接总数)、idleConnectionCount()(空闲数)、evictAll()(清理所有空闲连接)。也可用 EventListener 观察 connection acquired/released 事件链路。

注意:想要让 HTTP/1.x 连接被复用,必须消费完响应体并关闭(例如 response.body?.close()),否则连接不会回到池中。

三、为什么默认是「5 个 / 5 分钟」

  • 面向“单用户应用”的折中:官方说明默认池“适合单用户应用”,当前“最多 5 个空闲连接、空闲 5 分钟后逐出”。这在复用命中率FD/内存占用之间平衡较好,也适配常见服务端的 Keep-Alive 策略(几十秒到数分钟)。

  • 充分利用连接复用带来的收益:更低的握手开销、更低时延/更高吞吐(避免频繁 TCP 慢启动)、省电。

四、什么时候需要调优(以及怎么调)

原则:先用默认值,只有当你的流量模型和默认假设不匹配时再调整。

场景 A:单域名高 QPS(HTTP/1.1)

  • 期望更高复用命中:把 maxIdleConnections 提到 10–20;keepAliveDuration 维持 5 分钟或根据服务端/NAT 超时调成 1–2 分钟,避免复用到“僵尸连接”。

  • 并发主要靠 Dispatcher:酌情提升 maxRequestsPerHost(默认 5)和 maxRequests(默认 64)。

场景 B:多域名(微服务/多后端),请求稀疏

  • 减少长期闲置 FD:将 keepAliveDuration 缩短到 30–120 秒,maxIdleConnections 也别太大,让冷连接尽快退出(命中率和资源占用做权衡)。相关讨论也建议基于流量分布试验而非一刀切。

场景 C:大量 OkHttpClient 实例

  • 避免“池碎片化”:优先单例/共享 OkHttpClient;需要差异化配置用 client.newBuilder() 派生,保证复用同一个连接池

场景 D:你想“限制连接总数”

  • 别用连接池来限流(它只管空闲连接); Dispatcher 控制异步并发,或限制你用于同步 execute() 的业务线程池大小。

五、与线程池/并发的关系(容易混淆的点)

  • 连接池 ≠ 并发控制:连接池只保存可复用的空闲连接;真正的“并发多少个请求”由 Dispatcher 的线程池和并发上限决定。

  • HTTP/2 下,同一连接可多路复用,多数情况下不需要为同一主机保留太多空闲连接。

六、实用清单(落地建议)

  • 移动端/弱网:谨慎把 keepAliveDuration 设太长,部分 NAT/中间设备会在数十秒~数分钟内静默回收空闲连接。
  • 观测:结合 connectionCount()/idleConnectionCount() 与 EventListener 的 connectionAcquired/Released 事件做水位与健康监控。
  • 强制清理:evictAll() 只能清空空闲连接;正在用的连接不会被强踢,需等待其变空闲。