OkHttp 系列七:连接池与请求服务拦截器

2,647 阅读5分钟

连接池与请求服务拦截器

本文概述:

  • 文章讨论了OkHttp 中的连接池与请求服务拦截器;在连接池中介绍了默认连接池、连接池优化思路、怎么从连接池查找连接、连接池工作流程;简单介绍了请求服务拦截器;

连接池:

什么是连接池:

  • 对象池

保存的是什么:已经与特定服务端建立好连接的对象,去准备复用

  • 需要用容器去保存:OkHttp 中提供了连接对象复用池(RealConnectionPool ),内部有属性connections ,这个东西就是装连接的对象

     //线程安全的队列:连接池所有的连接都保存在里面
     private val connections = ConcurrentLinkedQueue<RealConnection>()
    
  • 需要提供的方法:get/put

    • get:

    • put:向连接池添加连接对象 + 启动周期性任务(会执行cleanup )

       fun put(connection: RealConnection) {
           connection.assertThreadHoldsLock()
       ​
           connections.add(connection)
           cleanupQueue.schedule(cleanupTask)
       }
      
      • cleanup :清理无效(不健康 )的连接

什么时候工作?

  • 在新建连接之前,回去连接池中查找有没有现成的连接(与本次目标主机相同的已经建立好的连接 ),连接池中拿不到才去新建连接;

OkHttp 允许配置连接池:

  • 配置示意:

     var okHttpClient = OkHttpClient.Builder()
     .connectionPool(ConnectionPool())
    

默认的连接池是怎么玩的?

  • 示意图:

image-20220731001610511

  • 参数:

    • maxIdleConnections :连接池最大允许的空闲连接数,默认5
    • keepAliveDuration :连接最大允许的空闲时间,默认5
    • timeUnit :时间的单位(默认为分钟)

OkHttp 对连接池的优化思路:cleanup

  • 根据连接池中的连接对象闲置了多久

    • 指定闲置时间限制:超过了,就清理掉
    • 默认为5 min
  • 连接池中存放了大量的空闲连接对象

    • 此时没有正在使用,
    • 将闲置时间最长的清理掉,知道不超过5 个连接(LRU 思想)
  • cleanup 具体的业务逻辑

    1. 遍历所有的连接,记录正在使用的连接数与空闲的连接数(会记录空闲时间)

       // 记录正在使用与已经闲置的连接数
       if (pruneAndGetAllocationCount(connection, now) > 0) {
           inUseConnectionCount++
       } else {
           idleConnectionCount++
      
    2. 对闲置时间最长的连接,记录其对象名与闲置时间;

       // 记录最长闲置时间的连接longestIdleConnection
       // If the connection is ready to be evicted, we're done.
       val idleDurationNs = now - connection.idleAtNs
       if (idleDurationNs > longestIdleDurationNs) {
           longestIdleDurationNs = idleDurationNs
           longestIdleConnection = connection
       } else {
           Unit
       }
      
    3. 最长闲置时间的连接超过了允许闲置时间 或者 闲置数量超过允许数量,从最长闲置连接开始清理

      • 从连接池中清理掉,同时关闭其Socket 连接

      • 不满足清理条件:

        • 会做一次时间的更新

          • 例如:配置最大闲置时间为五分钟,本次最长闲置时间为1分钟,那么将其剩余时间更新为四分钟;那么,在四分钟之后就可能去清除这个
        • 如果当前连接池中没有闲置的连接,检查有没有正在使用的连接;有,当前的任务在五分钟之后才可能去清理

        • 都没有:不清理

怎么从连接池中查找连接:callAcquirePooledConnection

  • 返回布尔值:

    • true :成功得到一个连接,并将其保存在call 里面去;
  • 是怎么得到连接的?

    • 对连接池遍历,判断端口、代理、协议版本信息(HTTP1.X 不能拿HTTP 2.0的)、等信息一致,那么就代表可以复用,将其保存在RealCall 对象里面去,完成本次的请求;

连接池的工作流程:

  • 新建连接使用后,如果不是一次性的连接(Keep-Alive),将其放入连接池并启动cleanup,这个周期性任务会去清理掉多余的连接以及闲置时间过长的连接;这个任务的周期取决于闲置时间,最大默认为5 分钟,若本轮遍历得到的最大闲置时间为1 分钟,那么在4 分钟后会去重新启动这个任务尝试执行清理操作;
  • 一次性连接(<connection,close>)

请求服务拦截器:

干了什么:

  • 拿到上一个拦截器处理好的连接以及request 对象执行writeRequestHeaders 方法

     exchange.writeRequestHeaders(request)
    
    • writeRequestHeaders :利用Socket 完成向服务器发送请求行、请求头

      • 但对于post 请求,还少了一个请求体;因此其内部做了判断

         if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
        
        • 如果本次请求不是GET 、HEAD 而是有请求体的方法 (requestBody != null),那么会判断本次请求的请求体是否包含字段Expect
       if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
      
    • 客户端通过readResponseHeaders 解析响应服务端数据成OkHttp 中的Response 对象

      • HTTP 1.X ---> 文本
     if (responseBuilder == null) {
       responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
    
    • 还会做一些判定:

      • 连接不持久:将此连接的本地Socket 清理并且不会将这个连接放入连接池
      • 还会有协议冲突:抛出对应的异常
    • 没有特殊情况,那么将这个Response 依次向上抛出

HTTP 请求补充:

  • 业务场景:客户端需要向服务端发送一个大数据或者需要验证,那么客户端会去先询问服务器是否愿意接收请求体的数据;服务端允许,响应100,此时客户端就发送那个大数据;服务端不允许,那这次请求就中断掉;

  • 但是,服务器可能会忽略掉此请求头,一直无法应答,此时请求超时

    • 所以,一般还是不用这个东西