PAYATHON 2026

OkHttp 5.3.2 如何设置 ConnectionPool 中连接的最大存活时间(TTL)?

数据小许

结论

OkHttp 5.3.2 没有公开的连接最大存活时间(TTL)配置,也没有公开的连接池策略接口,允许按连接年龄决定是否继续复用。ConnectionPool 的 keepAliveDuration 控制空闲连接的保留时间,无法实现“连接存活达到 30 秒后,等待正在进行的请求结束再回收”。该版本中,带有 ConnectionListener 的构造函数仍属于内部接口。ConnectionPool 5.3.2 源码

公开的 EventListener 可以观察连接的获取和释放,但仅靠监听回调无法完整、安全地实现上述 TTL 策略。

为什么设置 30 秒空闲超时不等于 TTL?

下面的配置只表示连接空闲达到 30 秒后可以被清理:

import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

val pool = ConnectionPool(5, 30, TimeUnit.SECONDS)
val client = OkHttpClient.Builder()
    .connectionPool(pool)
    .build()

每次连接结束使用、重新变为空闲时,空闲计时都会重新开始。只要连接持续被复用且空闲间隔足够短,它的总存活时间就可能远超 30 秒。清理任务并非实时定时器,不能保证在第 30 秒精确关闭连接。连接释放逻辑、连接池清理逻辑

能否通过 Listener 自行关闭过期连接?

公开的 EventListener 提供 connectionAcquired(call, connection) 和 connectionReleased(call, connection),适合记录连接复用情况和调用指标。它们只提供事件通知,没有“拒绝复用”或“释放后关闭”的返回值。回调约定要求快速执行,不修改事件参数,也不重入客户端。EventListener 5.3.2 源码

需要注意,connectionReleased 表示某个 Call 已释放连接,并不意味着整个连接已经空闲。HTTP/2 连接可能仍有其他并发请求在使用。该回调发生在连接释放处理之后,不能作为归还连接池前的原子决策点。RealCall 5.3.2 源码

直接在回调里调用 connection.socket().close() 会立即关闭底层连接,可能中断共享该连接的其他请求,因此不应这样模拟优雅回收。公开的 Connection 接口也没有 noNewExchanges() 方法。Connection 5.3.2 源码

必须实现 TTL 时,需要改动哪里?

OkHttp 内部的 RealConnection.noNewExchanges() 可以将连接标记为不再接受新的 exchange。最后一个占用者释放连接后,连接池会根据该标记移除连接,再由释放流程关闭 socket。这属于内部实现机制,不是稳定的公开扩展接口。如果设置标记时连接已经空闲,也不意味着连接会立即被移除并关闭。RealConnection 源码、连接变为空闲时的处理

如果业务必须满足 TTL 要求,可以维护定制版本,在连接建立、获取和清理流程中实现相应策略:

  1. 在连接建立成功时记录单调时钟时间,明确 TTL 的起算点。
  2. 获取连接时,在连接锁保护下检查年龄,避免继续分配已经过期的连接。
  3. 到期时,如果连接正在使用,阻止后续复用并等待已有占用释放;如果连接已经空闲,安全地将其移出连接池并关闭。
  4. 将 TTL 到期时间纳入清理调度,否则长期没有新请求的空闲连接可能无法及时按 TTL 回收。

HTTP/2 场景需要等待所有已有占用释放,不能只等待触发检查的那个请求。还需要明确重定向、认证重试等后续 exchange 的处理方式。停止创建新 exchange 和保证整个逻辑 Call 始终使用原连接,是两个不同的约束。以上属于定制实现设计,不能当作 5.3.2 已提供的配置能力。

只使用公开 API 时有哪些替代方案?

如果可以接受不保留空闲连接,可以设置:

val pool = ConnectionPool(0, 30, TimeUnit.SECONDS)

这里起作用的是 maxIdleConnections = 0:连接变为空闲时就会被移除。这会增加建立连接的开销,也不会限制仍在被并发使用的 HTTP/2 连接的总寿命。

还可以主动调用 pool.evictAll(),清理调用时的全部空闲连接。它不会按连接年龄筛选,也不会让正在使用的连接在稍后释放时自动过期。定期执行只能作为批量清理措施,不能代替每条连接独立的 TTL。ConnectionPool 接口及内部回收实现

备注:内容仅供参考。