Okhttp3 链接池复用机制源码探索
前文
對于http請求我們都知道開始于TCP鏈接的三次握手然后傳輸數據然后釋放,如下圖
而當我們開啟連接復用keep-alive后就是指在上一次鏈接不立馬斷開鏈接在超時范圍內復用connection在timeout 空閑的時間內就會復用相同的Request來減少握手大幅度提高了網絡請求效率;如下圖
而在Okhttp3中是怎么做到連接池復用的,本文從源碼(版本v4.9.3)角度來進行探索
Okhttp3的連接池復用、清理、回收機制
連接池的代碼類位于okhttp3.ConnectionPool,該類作為默認ConnectionPool 有5個空閑狀態的鏈接和默認5min的超時設置,如果要更改可以通OkHttpClient.Builder().connectionPool() 來配置更改
class ConnectionPool internal constructor(internal val delegate: RealConnectionPool ) {constructor(maxIdleConnections: Int,keepAliveDuration: Long,timeUnit: TimeUnit) : this(RealConnectionPool(taskRunner = TaskRunner.INSTANCE,maxIdleConnections = maxIdleConnections,keepAliveDuration = keepAliveDuration,timeUnit = timeUnit))constructor() : this(5, 5, TimeUnit.MINUTES)//fun evictAll() {delegate.evictAll()} }實際對連接池的管理代碼是在okhttp3.internal.connection.RealConnectionPool 該類管理當用戶發起請求時判斷是否有可以復用的connection;對connection進行緩存、獲取、清理回收的操作
// 存儲RealConnection的雙向隊列,并在添加或者刪除時持有鎖可以防止同時被刪除或采用
private val connections = ConcurrentLinkedQueue<RealConnection>()緩存
添加緩存是在put方法,添加完成后并會進行一次清理操作(清理在下面說到)
fun put(connection: RealConnection) {connection.assertThreadHoldsLock()connections.add(connection)// 添加到cleanupQueue循環執行,如果task已經存在則使用最早的時間執行cleanupQueue.schedule(cleanupTask) }獲取
fun callAcquirePooledConnection(doExtensiveHealthChecks: Boolean,address: Address,call: RealCall,routes: List<Route>?,requireMultiplexed: Boolean ): RealConnection? {for (connection in connections) {// In the first synchronized block, acquire the connection if it can satisfy this call.val acquired = synchronized(connection) {when {// 要求多路復用且不是HTTP2的返回falserequireMultiplexed && !connection.isMultiplexed -> false//判斷是否可以跟緩存的鏈接復用(ip、prxy_type等)!connection.isEligible(address, routes) -> falseelse -> {// 可以復用call.acquireConnectionNoEvents(connection)true}}}if (!acquired) continue// 檢查該鏈接是否健康if (connection.isHealthy(doExtensiveHealthChecks)) return connection// 釋放不健康的connection...val toClose: Socket? = synchronized(connection) {connection.noNewExchanges = truecall.releaseConnectionNoEvents()}toClose?.closeQuietly()}return null }復用判斷isEligible() 在該方法判斷是否可以復用,在該方法里進行了各種判斷如下,判斷成功后才可進行復用
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {assertThreadHoldsLock()// 負載超過最大限制,不復用if (calls.size >= allocationLimit || noNewExchanges) return false// 主機字段不一樣,不復用if (!this.route.address.equalsNonHost(address)) return false// 主機完全匹配,則可以復用if (address.url.host == this.route().address.url.host) {return true // This connection is a perfect match.}// 1.判斷是不是Http2if (http2Connection == null) return false// 2. 判斷地址是不是同一個ipif (routes == null || !routeMatchesAny(routes)) return false// 3. 鏈接的服務器證書可以覆蓋新的主機if (address.hostnameVerifier !== OkHostnameVerifier) return falseif (!supportsUrl(address.url)) return false// 4. 證書是否匹配try {address.certificatePinner!!.check(address.url.host, handshake()!!.peerCertificates)} catch (_: SSLPeerUnverifiedException) {return false}return true }總結 獲取復用的鏈接池的步驟為
- 判斷:要求多路復用且不是HTTP2的返回null
- 判斷:當前請求是否可以跟緩存池里的concection復用,通過isEligible() 方法來判斷
- 可以復用,調用acquireConnectionNoEvents()
- 檢查鏈接是否健康可以被使用,
- 可以則返回
- 不可以則清除,返回null
鏈接池的清理和回收
鏈接池的清理是在cleanupQueue一個循環執行,并可以設置延時時間執行的task的線程池里操作的Queue:
cleanupTask
清理cleanup
cleanup是來執行清理的方法,該方法主要就是GC的標記清除算法,先標記后清除;判斷能不能清除是通過弱引用來判斷的
fun cleanup(now: Long): Long {var inUseConnectionCount = 0var idleConnectionCount = 0//長閑置的鏈接var longestIdleConnection: RealConnection? = nullvar longestIdleDurationNs = Long.MIN_VALUE// 循環當前池子for (connection in connections) {synchronized(connection) {// 通過弱引用來判斷是否在使用if (pruneAndGetAllocationCount(connection, now) > 0) {inUseConnectionCount++} else {idleConnectionCount++// 計算這個鏈接閑置了多久val idleDurationNs = now - connection.idleAtNsif (idleDurationNs > longestIdleDurationNs) {longestIdleDurationNs = idleDurationNslongestIdleConnection = connection} else Unit}}}when {// 判斷是否超過了保活時間或者池內數量超過了限制數量,則立馬移除longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections -> {val connection = longestIdleConnection!!synchronized(connection) {if (connection.calls.isNotEmpty()) return 0L // No longer idle.if (connection.idleAtNs + longestIdleDurationNs != now) return 0L // No longer oldest.connection.noNewExchanges = trueconnections.remove(longestIdleConnection)}connection.socket().closeQuietly()if (connections.isEmpty()) cleanupQueue.cancelAll()// Clean up again immediately.return 0L}idleConnectionCount > 0 -> {// 池內存在閑置的鏈接數量,在繼續等待;等待時間為:保活時間-最大閑置時間return keepAliveDurationNs - longestIdleDurationNs}inUseConnectionCount > 0 -> {// 有使用中的鏈接,再等待一個保活時間return keepAliveDurationNs}else -> {// 都不滿足,沒有在使用;則被清理return -1}} }總結:cleanup的主要邏輯為
- 判斷閑置的鏈接是否如果大于超時時間或者閑置鏈接的最大數量則進行清理
- 返回其下次執行清理的時間間隔,條件為
- 如果閑置的連接數大于0就返回用戶設置的允許限制的時間-閑置時間最長的那個連接的閑置時間。
- 如果清理失敗就返回-1
- 如果清理成功就返回0
- 如果沒有閑置的鏈接就直接返回用戶設置的最大空閑時間間隔(默認5min)
回收pruneAndGetAllocationCount
pruneAndGetAllocationCount() 方法是來判斷當前鏈接是否在被使用,沒有則進行回收;
而這個鏈接被創建時會被放入弱引用,就是通過判斷這個弱引用來判斷鏈接是否還在使用
總結
- 獲取:在RealRoutePlanner類調用RealConnectionPool.callAcquirePooledConnection方法來獲取可復用的RealConnection類如果沒有可復用的則返回為空并重新創建一個新的RealConnection類
- put:在一個請求成功結束后在ConnectPaln.handleSuccess方法里會把當前的RealConnection類 put到RealConnectionPool里然后也會觸發清理緩存池的循環操作
- 清理:清理是在一個線程池里循環執行的,每次執行cleanup方法時會進行根據當前的空閑鏈接和等待時間計算下次執行的時間
總結
以上是生活随笔為你收集整理的Okhttp3 链接池复用机制源码探索的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于QtQuick的QCustomPlo
- 下一篇: amh升级php版本,AMH v45 更