精华内容
下载资源
问答
  • 程序又根据闲置时间对connection 选择了一个限制时间最长的链接,如果其大于keep_alive的极限时间(keepAliveDurationNs 5分钟),或者空闲链接个数大于连接的最大值(maxIdleConnections5个),则移除该...
     并向connection持有的allocations中增加了一条新的流的弱引用
    也就是往这条连接中增加了一条流。 
    ```
    
    而其中的条件为isEligible,跟踪进去可以看到,具体的条件为
    
    1.  当前这次连接的最大并发数没有达到上限
    2.  两个address的其他参数相同
    3.  两个address的url的host相同
    
    若满足以上条件,则说明host是相同的可以直接复用,如果不满足以上条件的话,仍旧有机会使用连接(将连接合并):
    
    1.  首先这个连接需要使用HTTP/2
    2.  要与复用前的Ip address相同,且不能使用代理
    3.  这个连接的服务器证书授权中,必须包括新的主机。
    4.  锁定证书(certificatePinner)必须匹配主机
    
    ```
    public boolean isEligible(Address address, @Nullable Route route) {
      // If this connection is not accepting new streams, we're done.
      if (allocations.size() >= allocationLimit || noNewStreams) return false;
    
      // If the non-host fields of the address don't overlap, we're done.
      if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
    
      // If the host exactly matches, we're done: this connection can carry the address.
      if (address.url().host().equals(this.route().address().url().host())) {
        return true; // This connection is a perfect match.
      }
    
      // At this point we don't have a hostname match. But we still be able to carry the request if
      // our connection coalescing requirements are met. See also:
      // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
      // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
    
      // 1. This connection must be HTTP/2.
      if (http2Connection == null) return false;
    
      // 2. The routes must share an IP address. This requires us to have a DNS address for both
      // hosts, which only happens after route planning. We can't coalesce connections that use a
      // proxy, since proxies don't tell us the origin server's IP address.
      if (route == null) return false;
      if (route.proxy().type() != Proxy.Type.DIRECT) return false;
      if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
      if (!this.route.socketAddress().equals(route.socketAddress())) return false;
    
      // 3. This connection's server certificate's must cover the new host.
      if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
      if (!supportsUrl(address.url())) return false;
    
      // 4. Certificate pinning must match the host.
      try {
        address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
      } catch (SSLPeerUnverifiedException e) {
        return false;
      }
    
      return true; // The caller's address can be carried by this connection.
    } 
    ```
    
    1. 连接池的清理和回收

      在put方法中已经用过了连接池的清理和回收 executor.execute(cleanupRunnable);现在变来详细看下其所做的事情:跟踪进入cleanupRunnable,发现其逻辑为定时执行cleanup,其中的定时等待是加入了同步锁,不允许打断。

      private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
          while (true) {
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
              long waitMillis = waitNanos / 1000000L;
              waitNanos -= (waitMillis * 1000000L);
              synchronized (ConnectionPool.this) {
                try {
                  ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                } catch (InterruptedException ignored) {
                }
              }
            }
          }
        }
      }; 
      

      下面聚焦的重点clean up之中。总的来说他的作用是找到限制的链接并清理,具体分析可以发现:

      • 其通过inUseConnectionCount记录正在使用的链接数目,利用idleConnectionCount记录闲置的链接数。这两个链接数目的改变,都是通过pruneAndGetAllocationCount()方法控制的,起作用也就自然而然为判断传入的链接是闲置的还是运行的。
      • 程序又根据闲置时间对connection 选择了一个限制时间最长的链接,如果其大于keep_alive的极限时间(keepAliveDurationNs 5分钟),或者空闲链接个数大于连接池的最大值(maxIdleConnections5个),则移除该connection
      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;
      } 
      

      跟踪进去,发现其是通过维护Reference类型的链表(references)达到效果的。起作用为记录connection 活跃情况的(>0 表示活跃=0 表示空闲)

      整体逻辑的核心为:如果 StreamAlloction 引用空闲,但是connection的引用列表中仍旧存在该项,那么便发生了内存泄露

      //用于清理可能泄露的 StreamAllocation并返回正在使用此连接的StreamA1location的数量,
      private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        List<Reference<StreamAllocation>> references = connection.allocations;
        for (int i = 0; i < references.size(); ) {
          Reference<StreamAllocation> reference = references.get(i);
      
          if (reference.get() != null) {
            i++;
            continue;
          } //表示该流活跃
      
          // We've discovered a leaked allocation. This is an application bug.
          StreamAllocation.StreamAllocationReference streamAllocRef =
              (StreamAllocation.StreamAllocationReference) reference;
          String message = "A connection to " + connection.route().address().url()
              + " was leaked. Did you forget to close a response body?";
          Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
      
          references.remove(i);
          connection.noNewStreams = true;
      
          // If this was the last allocation, the connection is eligible for immediate eviction.
          if (references.isEmpty()) {
      
      
    展开全文
  • 本系列文章: OkHttp源码彻底解析(一)OkHttp请求流程 OkHttp源码彻底解析(二)OkHttp架构及API源码 ...OkHttp源码彻底解析(五)OkHttp连接 目录 OkHttp连接 连接的意义——KeepAl...

    本系列文章:

    OkHttp源码彻底解析(一)OkHttp请求流程

    OkHttp源码彻底解析(二)OkHttp架构及API源码

    OkHttp源码彻底解析(三)OkHttp3.0拦截器原理——责任链模式

    OkHttp源码彻底解析(四)OkHttp拦截器的作用

    OkHttp源码彻底解析(五)OkHttp连接池

    目录

    OkHttp连接池

    连接池的意义——KeepAlive机制

    从拦截器流程了解连接池

    连接池ConnectionPool的创建

    连接池的缓存操作


    连接池是用来管理和复用网络连接对象的,而网络连接的主角就是Connection/RealConnection.

    OkHttp连接池

    OkHttp3将客户端与服务器之间的连接抽象为Connection/RealConnection,为了管理这些连接的复用而设计了ConnectionPool。共享相同Address的请求可以复用连接

    连接池的意义——KeepAlive机制

    复用连接,减少了频繁的网络请求导致性能下降的问题。我们知道,Http是基于TCP协议的,而TCP建立连接需要经过三次握手,断开需要经过四次挥手,因此,Http中添加了一种KeepAlive机制,当数据传输完毕后仍然保持连接,等待下一次请求时直接复用该连接。

    一次响应的流程

    在高并发的请求连接情况下或者同个客户端多次频繁的请求操作,无限制的创建会导致性能低下。

    如果使用keep-alive

    timeout空闲时间内,连接不会关闭,相同重复的request将复用原先的connection,减少握手的次数,大幅提高效率。

    并非keep-alive的timeout设置时间越长,就越能提升性能。长久不关闭会造成过多的僵尸连接和泄露连接出现。

    从拦截器流程了解连接池

    在讲解连接池之前,先了解OkHttp的拦截器

    OkHttp拦截器流程图

    上面的拦截器分别是:

    1.失败重连拦截器:
    一个循环来不停的获取response。每循环一次都会获取下一个request,如果没有,则返回response,退出循环。而获取下一个request的逻辑,是根据上一个response返回的状态码,分别作处理。

    2.桥接拦截器:

    请求从应用层数据类型类型转化为网络调用层的数据类型。将网络层返回的数据类型 转化为 应用层数据类型。(补足缺失的请求头等)

    3.缓存拦截器:

    CacheInterceptor主要作用是将请求 和 返回 关连得保存到缓存中。客户端与服务端根据一定的机制,在需要的时候使用缓存的数据作为网络请求的响应,节省了时间和带宽。

    4.连接拦截器 

    与请求服务器的拦截器是网络交互的关键。为请求服务器拦截器建立可用的连接,创建用于网络IO流  的RealConnection对象

    5.请求服务器的拦截器:

    完成了最后发起网络请求的工作。将HTTP请求写入网络IO流,从IO流读取网络数据

    与连接拦截器要划分为两个拦截器,除了解耦之外,更重要的是在这两个流程之间还可以插入一个专门为WebSocket服务的拦截器( WebSocket一种在单个 TCP 连接上进行全双工通讯的协议,本文不做详解)。
    关于这部分不具体展开,感兴趣可以看我的另外两篇博客,可以说是非常详细地介绍了拦截器的原理及应用

    OkHttp拦截器

    拦截器原理

    其中,与连接池相关的是失败重连拦截器连接拦截器

    1.RetryAndFollowUpInterceptor将创建的StreamAllocation对象传递给后面执行的Interceptor

    2.ConnectInterceptorRealInterceptorChain获取前面的Interceptor传过来的StreamAllocation对象,执行 streamAllocation.newStream() 完成前述所有的连接建立工作,创建的用于网络IO的RealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的Interceptor,也就是CallServerInterceptor

    streamAllocation.newStream()新建了IO流,将请求序列化发到网络,将网络数据反序列化并接收(请求服务器的拦截器)

    RealConnection底层连接着Socket,就是实现了跨进程与网络上其他设备交互的底层实现。

    连接池ConnectionPool的创建

    OkHttp3的用户可以自行创建ConnectionPool,对最大空闲连接数及连接的保活时间进行配置,并在OkHttpClient创建期间,将其传给OkHttpClient.Builder,在OkHttpClient中启用它。没有定制连接池的情况下,则在OkHttpClient.Builder构造过程中以默认参数

    默认情况下,ConnectionPool 最多保存 5个 处于空闲状态的连接,且连接的默认保活时间为 5分钟。也就是默认支持5个并发Socket连接,默认的keepAlive时间为5分钟,当然我们可以在构建OkHttpClient时设置不同的值

    连接池的缓存操作

    ConnectionPool提供对Deque<RealConnection>进行操作的方法分别为putgetconnectionBecameIdleevictAll几个操作。分别对应放入连接、获取连接、移除连接、移除所有连接操作。

    遍历connections缓存列表,当某个连接计数的次数小于限制的大小以及request的地址和缓存列表中此连接的地址完全匹配。则直接复用缓存列表中的connection作为request的连接。

    展开全文
  • okhttp连接复用机制

    万次阅读 多人点赞 2016-06-21 18:26:41
    connections : connection缓存。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。 routeDatabase :用来记录连接失败router 2.1 缓存操作 ConnectionPool ...

    1、概述

    提高网络性能优化,很重要的一点就是降低延迟和提升响应速度

    通常我们在浏览器中发起请求的时候header部分往往是这样的

    浏览器发起请求header

    keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可以复用的。在HTTP1.1中是默认开启的。

    连接的复用为什么会提高性能呢?
    通常我们在发起http请求的时候首先要完成tcp的三次握手,然后传输数据,最后再释放连接。三次握手的过程可以参考这里 TCP三次握手详解及释放连接过程

    一次响应的过程

    这里写图片描述

    在高并发的请求连接情况下或者同个客户端多次频繁的请求操作,无限制的创建会导致性能低下。

    如果使用keep-alive

    这里写图片描述

    timeout空闲时间内,连接不会关闭,相同重复的request将复用原先的connection,减少握手的次数,大幅提高效率。

    并非keep-alive的timeout设置时间越长,就越能提升性能。长久不关闭会造成过多的僵尸连接和泄露连接出现。

    那么okttp在客户端是如果类似于客户端做到的keep-alive的机制。

    2、连接池的使用

    连接池的类位于okhttp3.ConnectionPool。我们的主旨是了解到如何在timeout时间内复用connection,并且有效的对其进行回收清理操作。

    其成员变量代码片

    /**
     * Background threads are used to cleanup expired connections. There will be at most a single
     * thread running per connection pool. The thread pool executor permits the pool itself to be
     * garbage collected.
       */
      private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
          Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
    
      /** The maximum number of idle connections for each address. */
      private final int maxIdleConnections;
    
      private final Deque<RealConnection> connections = new ArrayDeque<>();
      final RouteDatabase routeDatabase = new RouteDatabase();
      boolean cleanupRunning;
    • excutor : 线程池,用来检测闲置socket并对其进行清理。
    • connections : connection缓存池。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。
    • routeDatabase :用来记录连接失败router

    2.1 缓存操作

    ConnectionPool提供对Deque<RealConnection>进行操作的方法分别为putgetconnectionBecameIdleevictAll几个操作。分别对应放入连接、获取连接、移除连接、移除所有连接操作。

    put操作

    void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (!cleanupRunning) {
          cleanupRunning = true;
          executor.execute(cleanupRunnable);
        }
        connections.add(connection);
      }

    可以看到在新的connection 放进列表之前执行清理闲置连接的线程。

    既然是复用,那么看下他获取连接的方式。

    /** Returns a recycled connection to {@code address}, or null if no such connection exists. */
    RealConnection get(Address address, StreamAllocation streamAllocation) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
          if (connection.allocations.size() < connection.allocationLimit
              && address.equals(connection.route().address)
              && !connection.noNewStreams) {
            streamAllocation.acquire(connection);
            return connection;
          }
        }
        return null;
     }

    遍历connections缓存列表,当某个连接计数的次数小于限制的大小以及request的地址和缓存列表中此连接的地址完全匹配。则直接复用缓存列表中的connection作为request的连接。

    streamAllocation.allocations是个对象计数器,其本质是一个 List<Reference<StreamAllocation>> 存放在RealConnection连接对象中用于记录Connection的活跃情况。

    连接池中Connection的缓存比较简单,就是利用一个双端列表,配合CRD等操作。那么connectiontimeout时间类是如果失效的呢,并且如果做到有效的对连接进行清除操作以确保性能和内存空间的充足。

    2.2 连接池的清理和回收

    在看ConnectionPool的成员变量的时候我们了解到一个Executor的线程池是用来清理闲置的连接的。注释中是这么解释的:

    Background threads are used to cleanup expired connections

    我们在put新连接到队列的时候会先执行清理闲置连接的线程。调用的正是 executor.execute(cleanupRunnable); 方法。观察cleanupRunnable

    private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
          while (true) {
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
              long waitMillis = waitNanos / 1000000L;
              waitNanos -= (waitMillis * 1000000L);
              synchronized (ConnectionPool.this) {
                try {
                  ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                } catch (InterruptedException ignored) {
                }
              }
            }
          }
        }
      };

    线程中不停调用Cleanup 清理的动作并立即返回下次清理的间隔时间。继而进入wait 等待之后释放锁,继续执行下一次的清理。所以可能理解成他是个监测时间并释放连接的后台线程。

    了解cleanup动作的过程。这里就是如何清理所谓闲置连接的和行了。怎么找到闲置的连接是主要解决的问题。

    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;
      }

    在遍历缓存列表的过程中,使用连接数目inUseConnectionCount 和闲置连接数目idleConnectionCount 的计数累加值都是通过pruneAndGetAllocationCount() 是否大于0来控制的。那么很显然pruneAndGetAllocationCount() 方法就是用来识别对应连接是否闲置的。>0则不闲置。否则就是闲置的连接。

    进去观察

     private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        List<Reference<StreamAllocation>> references = connection.allocations;
        for (int i = 0; i < references.size(); ) {
          Reference<StreamAllocation> reference = references.get(i);
    
          if (reference.get() != null) {
            i++;
            continue;
          }
    
          // We've discovered a leaked allocation. This is an application bug.
          Platform.get().log(WARN, "A connection to " + connection.route().address().url()
              + " was leaked. Did you forget to close a response body?", null);
          references.remove(i);
          connection.noNewStreams = 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();
      }
    }

    好了,原先存放在RealConnection 中的allocations 派上用场了。遍历StreamAllocation 弱引用链表,移除为空的引用,遍历结束后返回链表中弱引用的数量。所以可以看出List<Reference<StreamAllocation>> 就是一个记录connection活跃情况的 >0表示活跃 =0 表示空闲。StreamAllocation 在列表中的数量就是就是物理socket被引用的次数

    解释:StreamAllocation被高层反复执行aquirerelease。这两个函数在执行过程中其实是在一直在改变Connection中的 List<WeakReference<StreamAllocation>>大小。

    搞定了查找闲置的connection操作,我们回到cleanup 的操作。计算了inUseConnectionCountidleConnectionCount 之后程序又根据闲置时间对connection进行了一个选择排序,选择排序的核心是:

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

    通过对比最大闲置时间选择排序可以方便的查找出闲置时间最长的一个connection。如此一来我们就可以移除这个没用的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);
    }

    总结:清理闲置连接的核心主要是引用计数器List<Reference<StreamAllocation>>选择排序的算法以及excutor的清理线程池。

    部分参考:http://www.jianshu.com/p/92a61357164b

    展开全文
  • 连接的类位于okhttp3.ConnectionPool。我们的主旨是了解到如何在timeout时间内复用connection,并且有效的对其进行回收清理操作。 其成员变量代码片 /** * Background threads are used to cleanup expired ...

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    1、概述

    提高网络性能优化,很重要的一点就是降低延迟和提升响应速度。

    通常我们在浏览器中发起请求的时候header部分往往是这样的

    keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可以复用的。在HTTP1.1中是默认开启的。

    连接的复用为什么会提高性能呢?
    通常我们在发起http请求的时候首先要完成tcp的三次握手,然后传输数据,最后再释放连接。三次握手的过程可以参考这里 TCP三次握手详解及释放连接过程

    一次响应的过程

    在高并发的请求连接情况下或者同个客户端多次频繁的请求操作,无限制的创建会导致性能低下。

    如果使用keep-alive

    在timeout空闲时间内,连接不会关闭,相同重复的request将复用原先的connection,减少握手的次数,大幅提高效率。

    并非keep-alive的timeout设置时间越长,就越能提升性能。长久不关闭会造成过多的僵尸连接和泄露连接出现。

    那么okttp在客户端是如果类似于客户端做到的keep-alive的机制。

    2、连接池的使用

    连接池的类位于okhttp3.ConnectionPool。我们的主旨是了解到如何在timeout时间内复用connection,并且有效的对其进行回收清理操作。

    其成员变量代码片

    /**
     * Background threads are used to cleanup expired connections. There will be at most a single
     * thread running per connection pool. The thread pool executor permits the pool itself to be
     * garbage collected.
       */
      private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
          Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
    
      /** The maximum number of idle connections for each address. */
      private final int maxIdleConnections;
    
      private final Deque<RealConnection> connections = new ArrayDeque<>();
      final RouteDatabase routeDatabase = new RouteDatabase();
      boolean cleanupRunning;
    
    

    excutor : 线程池,用来检测闲置socket并对其进行清理。
    connections : connection缓存池。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。
    routeDatabase :用来记录连接失败router

    2.1 缓存操作

    ConnectionPool提供对Deque进行操作的方法分别为put、get、connectionBecameIdle、evictAll几个操作。分别对应放入连接、获取连接、移除连接、移除所有连接操作。

    put操作

    void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (!cleanupRunning) {
          cleanupRunning = true;
          executor.execute(cleanupRunnable);
        }
        connections.add(connection);
      }
    
    

    可以看到在新的connection 放进列表之前执行清理闲置连接的线程。

    既然是复用,那么看下他获取连接的方式。

    /** Returns a recycled connection to {@code address}, or null if no such connection exists. */
    RealConnection get(Address address, StreamAllocation streamAllocation) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
          if (connection.allocations.size() < connection.allocationLimit
              && address.equals(connection.route().address)
              && !connection.noNewStreams) {
            streamAllocation.acquire(connection);
            return connection;
          }
        }
        return null;
     }
    
    

    遍历connections缓存列表,当某个连接计数的次数小于限制的大小以及request的地址和缓存列表中此连接的地址完全匹配。则直接复用缓存列表中的connection作为request的连接。

    streamAllocation.allocations是个对象计数器,其本质是一个 List<Reference> 存放在RealConnection连接对象中用于记录Connection的活跃情况。

    连接池中Connection的缓存比较简单,就是利用一个双端列表,配合CRD等操作。那么connection在timeout时间类是如果失效的呢,并且如果做到有效的对连接进行清除操作以确保性能和内存空间的充足。

    2.2 连接池的清理和回收

    在看ConnectionPool的成员变量的时候我们了解到一个Executor的线程池是用来清理闲置的连接的。注释中是这么解释的:

    Background threads are used to cleanup expired connections

    我们在put新连接到队列的时候会先执行清理闲置连接的线程。调用的正是 executor.execute(cleanupRunnable); 方法。观察cleanupRunnable

    private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
          while (true) {
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
              long waitMillis = waitNanos / 1000000L;
              waitNanos -= (waitMillis * 1000000L);
              synchronized (ConnectionPool.this) {
                try {
                  ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                } catch (InterruptedException ignored) {
                }
              }
            }
          }
        }
      };
    
    

    线程中不停调用Cleanup 清理的动作并立即返回下次清理的间隔时间。继而进入wait 等待之后释放锁,继续执行下一次的清理。所以可能理解成他是个监测时间并释放连接的后台线程。

    了解cleanup动作的过程。这里就是如何清理所谓闲置连接的和行了。怎么找到闲置的连接是主要解决的问题。

    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;
      }
    
    

    在遍历缓存列表的过程中,使用连接数目inUseConnectionCount 和闲置连接数目idleConnectionCount 的计数累加值都是通过pruneAndGetAllocationCount() 是否大于0来控制的。那么很显然pruneAndGetAllocationCount() 方法就是用来识别对应连接是否闲置的。>0则不闲置。否则就是闲置的连接。

    进去观察

    private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        List<Reference<StreamAllocation>> references = connection.allocations;
        for (int i = 0; i < references.size(); ) {
          Reference<StreamAllocation> reference = references.get(i);
    
          if (reference.get() != null) {
            i++;
            continue;
          }
    
          // We've discovered a leaked allocation. This is an application bug.
          Platform.get().log(WARN, "A connection to " + connection.route().address().url()
              + " was leaked. Did you forget to close a response body?", null);
          references.remove(i);
          connection.noNewStreams = 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();
      }
    }
    
    

    好了,原先存放在RealConnection 中的allocations 派上用场了。遍历StreamAllocation 弱引用链表,移除为空的引用,遍历结束后返回链表中弱引用的数量。所以可以看出List<Reference> 就是一个记录connection活跃情况的 >0表示活跃 =0 表示空闲。StreamAllocation 在列表中的数量就是就是物理socket被引用的次数

    解释:StreamAllocation被高层反复执行aquire与release。这两个函数在执行过程中其实是在一直在改变Connection中的 List<WeakReference>大小。

    搞定了查找闲置的connection操作,我们回到cleanup 的操作。计算了inUseConnectionCount和idleConnectionCount 之后程序又根据闲置时间对connection进行了一个选择排序,选择排序的核心是:

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

    通过对比最大闲置时间选择排序可以方便的查找出闲置时间最长的一个connection。如此一来我们就可以移除这个没用的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);
    }
    

    总结:清理闲置连接的核心主要是引用计数器List<Reference> 和 选择排序的算法以及excutor的清理线程池。
    原文链接https://www.cnblogs.com/ganchuanpu/p/9408081.html
    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    展开全文
  • OkHttp连接源码追踪

    2020-05-21 19:46:04
    } public OkResponseResult doGet(OkHttpRequest okHttpRequest) { Headers.Builder headerBuilder = buildHeaders(okHttpRequest.headerParams(), okHttpRequest.retryTimes(), okHttpRequest.reqLogIgnore());...
  • OkHttp连接put和get方法: 在上一次【https://www.cnblogs.com/webor2006/p/9281429.html】咱们分析了连接拦截器,如下: 不管是Http1.0还是Http2.0,它们的keep-alive机制或者Http2.0的多路复用机制在实现上都...
  • okhttp3.ConnectionPool 。我们的主旨是了解到如何在timeout时间内复用connection,并且有效的对其进行回收清理操作。 其成员变量代码片 /* * * Background threads are used to cleanup expired connections...
  • 连接的类位于okhttp3.ConnectionPool。我们的主旨是了解到如何在timeout时间内复用connection,并且有效的对其进行回收清理操作。 其成员变量代码片 /** * Background threads are used to cleanup expired ...
  • no-no-no,这次我会整理点精华部分,让大家学习点东西,如标题所示,这次要讨论的话题是Okhttp的连接怎么工作的,以及它工作的原理,为什么要整理这篇文章呢,因为okhttp的连接在面试过程中很大可能被问到,因此...
  • okhttp3连接清理

    2020-10-02 23:03:06
    okhttp3连接清理okhttp3连接RealConnectionPool清理在什么时候触发cleanUp方法cleanUp方法的下半部分cleanUp方法的上半部份pruneAndGetAllocationCount使用okhttp注意点 okhttp3连接RealConnectionPool 连接...
  • okhttp中连接实现

    2019-08-29 16:14:56
    代码中包含okhhtp中连接的设计,包含连接对象的添加,连接对象何时被移除。
  • OkHttp3连接池原理:OkHttp3使用ConnectionPool连接池来复用链接,其原理是:当用户发起请求是,首先在链接池中检查是否有符合要求的链接(复用就在这里发生),如果有就用该链接发起网络请求,如果没有就创建一个...
  • okHttp3连接简单使用

    万次阅读 2018-11-18 23:05:55
    一、概述: ...这就是我们交换数据和媒体的方式。...连接减少了请求延迟(如果HTTP / 2不可用)。 透明GZIP缩小了下载大小。 响应缓存完全避免网络重复请求。 当网络很麻烦时,OkHttp坚持不懈:它将从常见的连...
  • 首先我们需要明白,okhttp比其他网络请求框架的优势在哪里?当然最重要的就是他对传输层的Socket进行了进一步的封装...所以okhttp就引出了连接的概念。所谓的连接就是为了复用socket,比如我们请求一个地址,如...
  • 文章目录OkHttp分层结构中流砥柱之StreamAllocation连接 ConnectionPoolRealConnection总结 OkHttp分层结构 OkHttp 分层结构如下图所示,其中连接部分位于我们的最底层连接层中。 在 OkHttp的拦截器中最后两个...
  • Okhttp的连接ConnectionPool(三)

    千次阅读 2018-11-02 10:47:15
    目录 1.get()方法 2.put() Okhttp3使用及解析:https://mp.csdn.net/postedit/83339916 okhttp系统拦截器:...Okhttp的连接ConnectionPool:https://mp.csdn.net/postedit/83650740   Okhttp...
  • OkHttp3——连接

    2020-10-15 16:07:31
    ConnectionPool --> RealConnectionPool(里面包含List<Realconnection>) 连接具有以下特点: 连接管理所有Socket连接,当有新...OkHttp真正的连接是在ConnectInterceptor中通过 Transmitter(realC...
  • OkHttp

    2017-01-03 16:16:17
    创建连接缓存响应数据来减少重复的网络请求 基于Http的文件上传 文件下载 加载图片 支持SPDY, Google开发的基于TCP的应用层协议 IP地址自动切换 GZip压缩减少传输的数据包大小, 文本压缩率极大 请求OkHttp必须创
  • 文章目录OKHttp的复用连接1. 主要变量与构造方法2. 缓存操作3. 自动回收连接4. 引用计数5. 小结 OKHttp的复用连接 为了解决TCP握手和挥手的效率问题,HTTP有一种叫做keepalive connections的机制,而OKHttp支持5...
  • Okhttp

    千次阅读 2016-10-22 15:16:34
    OkHttp 提供了对 HTTP/2 和 SPDY 的支持,并提供了连接,GZIP 压缩和 HTTP 响应缓存功能。OkHttp 的 API 接口也更加的简单实用,是一种http工具类。 2)在Android程序中经常需要用到 HTTP 客户端来发送 HTTP 请求...
  • okhttp详解之连接

    千次阅读 2018-11-22 10:54:58
    连接ConnectionPool的定义及其详解。 连接connect复用详解。 connet清理。 访问同一个地址的socket复用详解。 .服务器重新定位, 包括IP地址重新定位、代理proxy重新定位。也就是如果当集群服务器存在多个代理...
  • 上一篇我们根据OkHttp的源码讲解了OkHttp的请求网络流程,这一讲我们接着根据源码了解OkHttp的复用连接
  • 从上图其实也可以发现说每次请求生成StreamAllocation对象请求链接时,首先要做的不是new 一个新的RealConnection对象,而是从链接池中获取已经存在的并且可以复用的RealConnection,如果找不到可用的链接,则才new ...
  • OKHttp

    2017-02-27 15:23:50
    OkHttp官网地址:http://square.github.io/okhttp/ OkHttp GitHub地址:https://github.com/square/okhttp 官网的自我介绍: HTTP is the way modern applications network. It’s how we exchange ...
  • okhttp

    2016-08-08 14:37:25
    Android OkHttp官方教程解析-彻底入门OkHttp使用 OkHttp Android 2016-05-03 15:03:01 发布 您的评价:   0.0 收藏 3收藏  最近半年来身边开发的朋友越来越...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,586
精华内容 1,034
关键字:

okhttp链接池