Okhttp3 链接池复用机制源码探索
前文
對于http請求我們都知道開始于TCP鏈接的三次握手然后傳輸數(shù)據(jù)然后釋放,如下圖
而當(dāng)我們開啟連接復(fù)用keep-alive后就是指在上一次鏈接不立馬斷開鏈接在超時范圍內(nèi)復(fù)用connection在timeout 空閑的時間內(nèi)就會復(fù)用相同的Request來減少握手大幅度提高了網(wǎng)絡(luò)請求效率;如下圖
而在Okhttp3中是怎么做到連接池復(fù)用的,本文從源碼(版本v4.9.3)角度來進行探索
Okhttp3的連接池復(fù)用、清理、回收機制
連接池的代碼類位于okhttp3.ConnectionPool,該類作為默認ConnectionPool 有5個空閑狀態(tài)的鏈接和默認5min的超時設(shè)置,如果要更改可以通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 該類管理當(dāng)用戶發(fā)起請求時判斷是否有可以復(fù)用的connection;對connection進行緩存、獲取、清理回收的操作
// 存儲RealConnection的雙向隊列,并在添加或者刪除時持有鎖可以防止同時被刪除或采用
private val connections = ConcurrentLinkedQueue<RealConnection>()緩存
添加緩存是在put方法,添加完成后并會進行一次清理操作(清理在下面說到)
fun put(connection: RealConnection) {connection.assertThreadHoldsLock()connections.add(connection)// 添加到cleanupQueue循環(huán)執(zhí)行,如果task已經(jīng)存在則使用最早的時間執(zhí)行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 {// 要求多路復(fù)用且不是HTTP2的返回falserequireMultiplexed && !connection.isMultiplexed -> false//判斷是否可以跟緩存的鏈接復(fù)用(ip、prxy_type等)!connection.isEligible(address, routes) -> falseelse -> {// 可以復(fù)用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 }復(fù)用判斷isEligible() 在該方法判斷是否可以復(fù)用,在該方法里進行了各種判斷如下,判斷成功后才可進行復(fù)用
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {assertThreadHoldsLock()// 負載超過最大限制,不復(fù)用if (calls.size >= allocationLimit || noNewExchanges) return false// 主機字段不一樣,不復(fù)用if (!this.route.address.equalsNonHost(address)) return false// 主機完全匹配,則可以復(fù)用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. 鏈接的服務(wù)器證書可以覆蓋新的主機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 }總結(jié) 獲取復(fù)用的鏈接池的步驟為
- 判斷:要求多路復(fù)用且不是HTTP2的返回null
- 判斷:當(dāng)前請求是否可以跟緩存池里的concection復(fù)用,通過isEligible() 方法來判斷
- 可以復(fù)用,調(diào)用acquireConnectionNoEvents()
- 檢查鏈接是否健康可以被使用,
- 可以則返回
- 不可以則清除,返回null
鏈接池的清理和回收
鏈接池的清理是在cleanupQueue一個循環(huán)執(zhí)行,并可以設(shè)置延時時間執(zhí)行的task的線程池里操作的Queue:
cleanupTask
清理cleanup
cleanup是來執(zhí)行清理的方法,該方法主要就是GC的標(biāo)記清除算法,先標(biāo)記后清除;判斷能不能清除是通過弱引用來判斷的
fun cleanup(now: Long): Long {var inUseConnectionCount = 0var idleConnectionCount = 0//長閑置的鏈接var longestIdleConnection: RealConnection? = nullvar longestIdleDurationNs = Long.MIN_VALUE// 循環(huán)當(dāng)前池子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 {// 判斷是否超過了保活時間或者池內(nèi)數(shù)量超過了限制數(shù)量,則立馬移除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 -> {// 池內(nèi)存在閑置的鏈接數(shù)量,在繼續(xù)等待;等待時間為:保活時間-最大閑置時間return keepAliveDurationNs - longestIdleDurationNs}inUseConnectionCount > 0 -> {// 有使用中的鏈接,再等待一個保活時間return keepAliveDurationNs}else -> {// 都不滿足,沒有在使用;則被清理return -1}} }總結(jié):cleanup的主要邏輯為
- 判斷閑置的鏈接是否如果大于超時時間或者閑置鏈接的最大數(shù)量則進行清理
- 返回其下次執(zhí)行清理的時間間隔,條件為
- 如果閑置的連接數(shù)大于0就返回用戶設(shè)置的允許限制的時間-閑置時間最長的那個連接的閑置時間。
- 如果清理失敗就返回-1
- 如果清理成功就返回0
- 如果沒有閑置的鏈接就直接返回用戶設(shè)置的最大空閑時間間隔(默認5min)
回收pruneAndGetAllocationCount
pruneAndGetAllocationCount() 方法是來判斷當(dāng)前鏈接是否在被使用,沒有則進行回收;
而這個鏈接被創(chuàng)建時會被放入弱引用,就是通過判斷這個弱引用來判斷鏈接是否還在使用
總結(jié)
- 獲取:在RealRoutePlanner類調(diào)用RealConnectionPool.callAcquirePooledConnection方法來獲取可復(fù)用的RealConnection類如果沒有可復(fù)用的則返回為空并重新創(chuàng)建一個新的RealConnection類
- put:在一個請求成功結(jié)束后在ConnectPaln.handleSuccess方法里會把當(dāng)前的RealConnection類 put到RealConnectionPool里然后也會觸發(fā)清理緩存池的循環(huán)操作
- 清理:清理是在一個線程池里循環(huán)執(zhí)行的,每次執(zhí)行cleanup方法時會進行根據(jù)當(dāng)前的空閑鏈接和等待時間計算下次執(zhí)行的時間
總結(jié)
以上是生活随笔為你收集整理的Okhttp3 链接池复用机制源码探索的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于QtQuick的QCustomPlo
- 下一篇: 褚时健