OkHttp 3.7源码分析(五)——连接池
来源:互联网 发布:2030男女比例真实数据 编辑:程序博客网 时间:2024/05/29 14:01
接下来讲下OkHttp的连接池管理,这也是OkHttp的核心部分。通过维护连接池,最大限度重用现有连接,减少网络连接的创建开销,以此提升网络请求效率。
1. 背景
1.1 keep-alive机制
在HTTP1.0中HTTP的请求流程如下:
这种方法的好处是简单,各个请求互不干扰。但在复杂的网络请求场景下这种方式几乎不可用。例如:浏览器加载一个HTML网页,HTML中可能需要加载数十个资源,典型场景下这些资源中大部分来自同一个站点。按照HTTP1.0的做法,这需要建立数十个TCP连接,每个连接负责一个资源请求。创建一个TCP连接需要3次握手,而释放连接则需要2次或4次握手。重复的创建和释放连接极大地影响了网络效率,同时也增加了系统开销。
为了有效地解决这一问题,HTTP/1.1提出了Keep-Alive
机制:当一个HTTP请求的数据传输结束后,TCP连接不立即释放,如果此时有新的HTTP请求,且其请求的Host通上次请求相同,则可以直接复用为释放的TCP连接,从而省去了TCP的释放和再次创建的开销,减少了网络延时:
在现代浏览器中,一般同时开启6~8个keepalive connections的socket连接,并保持一定的链路生命,当不需要时再关闭;而在服务器中,一般是由软件根据负载情况(比如FD最大值、Socket内存、超时时间、栈内存、栈数量等)决定是否主动关闭。
1.2 HTTP/2
在HTTP/1.x中,如果客户端想发起多个并行请求必须建立多个TCP连接,这无疑增大了网络开销。另外HTTP/1.x不会压缩请求和响应报头,导致了不必要的网络流量;HTTP/1.x不支持资源优先级导致底层TCP连接利用率低下。而这些问题都是HTTP/2要着力解决的。简单来说HTTP/2主要解决了以下问题:
- 报头压缩:HTTP/2使用HPACK压缩格式压缩请求和响应报头数据,减少不必要流量开销
- 请求与响应复用:HTTP/2通过引入新的二进制分帧层实现了完整的请求和响应复用,客户端和服务器可以将HTTP消息分解为互不依赖的帧,然后交错发送,最后再在另一端将其重新组装
- 指定数据流优先级:将 HTTP 消息分解为很多独立的帧之后,我们就可以复用多个数据流中的帧,客户端和服务器交错发送和传输这些帧的顺序就成为关键的性能决定因素。为了做到这一点,HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系
- 流控制:HTTP/2 提供了一组简单的构建块,这些构建块允许客户端和服务器实现其自己的数据流和连接级流控制
HTTP/2所有性能增强的核心在于新的二进制分帧层,它定义了如何封装HTTP消息并在客户端与服务器之间进行传输:
同时HTTP/2引入了三个新的概念:
- 数据流:基于TCP连接之上的逻辑双向字节流,对应一个请求及其响应。客户端每发起一个请求就建立一个数据流,后续该请求及其响应的所有数据都通过该数据流传输
- 消息:一个请求或响应对应的一系列数据帧
- 帧:HTTP/2的最小数据切片单位
上述概念之间的逻辑关系:
- 所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流
- 每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息
- 每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧
- 帧是最小的通信单位,承载着特定类型的数据,例如 HTTP 标头、消息负载,等等。 来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装
- 每个HTTP消息被分解为多个独立的帧后可以交错发送,从而在宏观上实现了多个请求或响应并行传输的效果。这类似于多进程环境下的时间分片机制
2. 连接池的使用与分析
无论是HTTP/1.1的Keep-Alive
机制还是HTTP/2的多路复用机制,在实现上都需要引入连接池来维护网络连接。接下来看下OkHttp中的连接池实现。
OkHttp内部通过ConnectionPool来管理连接池,首先来看下ConnectionPool的主要成员:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
相关概念:
Call
:对Http请求的封装Connection/RealConnection
:物理连接的封装,其内部有List<WeakReference<StreamAllocation>>
的引用计数StreamAllocation
: okhttp中引入了StreamAllocation负责管理一个连接上的流,同时在connection中也通过一个StreamAllocation的引用的列表来管理一个连接的流,从而使得连接与流之间解耦。关于StreamAllocation的定义可以看下这篇文章:okhttp源码学习笔记(二)– 连接与连接管理connections
: Deque双端队列,用于维护连接的容器routeDatabase
:用来记录连接失败的Route
的黑名单,当连接失败的时候就会把失败的线路加进去
2.1 实例化
首先来看下ConnectionPool的实例化过程,一个OkHttpClient只包含一个ConnectionPool,其实例化过程也在OkHttpClient的实例化过程中实现,值得一提的是ConnectionPool各个方法的调用并没有直接对外暴露,而是通过OkHttpClient的Internal接口统一对外暴露:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
这样做的原因是:
- 1
- 2
- 1
- 2
Internal的唯一实现在OkHttpClient中,OkHttpClient通过这种方式暴露其API给外部类使用。
2.2 连接池维护
ConnectionPool内部通过一个双端队列(dequeue)来维护当前所有连接,主要涉及到的操作包括:
- put:放入新连接
- get:从连接池中获取连接
- evictAll:关闭所有连接
- connectionBecameIdle:连接变空闲后调用清理线程
- deduplicate:清除重复的多路复用线程
2.2.1 StreamAllocation.findConnection
get是ConnectionPool中最为重要的方法,StreamAllocation
在其findConnection方法内部通过调用get方法为其找到stream找到合适的连接,如果没有则新建一个连接。首先来看下findConnection
的逻辑:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
其主要逻辑大致分为以下几个步骤:
- 查看当前streamAllocation是否有之前已经分配过的连接,有则直接使用
- 从连接池中查找可复用的连接,有则返回该连接
- 配置路由,配置后再次从连接池中查找是否有可复用连接,有则直接返回
- 新建一个连接,并修改其StreamAllocation标记计数,将其放入连接池中
- 查看连接池是否有重复的多路复用连接,有则清除
2.2.2 ConnectionPool.get
接下来再来看get方法的源码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
其逻辑比较简单,遍历当前连接池,如果有符合条件的连接则修改器标记计数,然后返回。这里的关键逻辑在RealConnection.isEligible
方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 连接没有达到共享上限
- 非host域必须完全一样
- 如果此时host域也相同,则符合条件,可以被复用
- 如果host不相同,在HTTP/2的域名切片场景下一样可以复用,具体细节可以参考:https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
2.2.3 deduplicate
deduplicate方法主要是针对在HTTP/2场景下多个多路复用连接清除的场景。如果当前连接是HTTP/2,那么所有指向该站点的请求都应该基于同一个TCP连接:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
put和evictAll比较简单,在这里就不写了,大家自行看源码。
2.3 自动回收
连接池中有socket回收,而这个回收是以RealConnection
的弱引用List<Reference<StreamAllocation>>
是否为0来为依据的。ConnectionPool有一个独立的线程cleanupRunnable
来清理连接池,其触发时机有两个:
- 当连接池中put新的连接时
- 当connectionBecameIdle接口被调用时
其代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这段死循环实际上是一个阻塞的清理任务,首先进行清理(clean),并返回下次需要清理的间隔时间,然后调用wait(timeout)
进行等待以释放锁与时间片,当等待时间到了后,再次进行清理,并返回下次要清理的间隔时间…
接下来看下cleanup函数:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
其基本逻辑如下:
- 遍历连接池中所有连接,标记泄露连接
- 如果被标记的连接满足(
空闲socket连接超过5个
&&keepalive时间大于5分钟
),就将此连接从Deque
中移除,并关闭连接,返回0
,也就是将要执行wait(0)
,提醒立刻再次扫描 - 如果(
目前还可以塞得下5个连接,但是有可能泄漏的连接(即空闲时间即将达到5分钟)
),就返回此连接即将到期的剩余时间,供下次清理 - 如果(
全部都是活跃的连接
),就返回默认的keep-alive
时间,也就是5分钟后再执行清理
而pruneAndGetAllocationCount
负责标记并找到不活跃连接:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
OkHttp的连接池通过计数+标记清理的机制来管理连接池,使得无用连接可以被会回收,并保持多个健康的keep-alive连接。这也是OkHttp的连接池能保持高效的关键原因。
转自:http://blog.csdn.net/asialiyazhou/article/details/72598365
- OkHttp 3.7源码分析(五)——连接池
- OkHttp 3.7源码分析(五)——连接池
- Okhttp源码分析(五)连接池
- OkHttp 3.7源码分析(一)——整体架构
- OkHttp 3.7源码分析(三)——任务队列
- OkHttp 3.7源码分析(四)——缓存策略
- OkHttp 3.7源码分析(一)——整体架构
- OkHttp 3.7源码分析(三)——任务队列
- OkHttp 3.7源码分析(四)——缓存策略
- OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现
- OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现
- OKHttp源码分析(一)
- OkHttp源码分析(一)
- OkHttp源码解析(五)——cache缓存
- Netty源码分析(五)—ByteBuf源码分析
- OKHttp源码解析二(复用连接池)
- OkHttp源码解析(三)——连接池复用
- OkHttp源码解析(三)——连接池复用
- 如何解决failed to push some refs to git
- 那些年关于Focusable和clickable的坑,完美解决。
- tensorflow中mnist手写数字识别
- OkHttp 3.7源码分析(四)——缓存策略
- mybatis学习之框架原理
- OkHttp 3.7源码分析(五)——连接池
- jquery选择器tr:even获取偶数行、tr:odd 获取奇数行
- linux下如何备份分区表
- Ubuntu 16.04 libwxgtk2.8-dev报错
- 人工智能时代下的视觉设计
- 安卓学习
- ORA-01000: 超出打开游标的最大数 问题的分析和解决
- VMware与主机共享文件夹--进行文件的拷贝操作
- 算法导论15章习题