天天看點

【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

作者:技術探究猿

OkHttp作為常用的網絡通訊元件,其中大部分的功能點需要我們深入了解,本系列的文章将以源碼角度解析元件背後的運作原理,避免踩坑。

【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

本篇幅通過源碼角度解釋一些使用不當導緻的”血案“。

Okhttp使用連接配接池複用連接配接,在HTTP1.1的版本複用的連接配接尤為重要,它避免了每次建立TCP連接配接的開銷。

如果你不熟悉Okhttp的連接配接池,在大量請求的情況下,它會拖慢你的性能,嚴重時會導緻一段時間機器無端口個可用。

一、先看現象,線上請求并發量上來後,大量的端口TIME_WAIT。請求建立連接配接無法申請到端口,等待作業系統釋放端口(net.ipv4.tcp_fin_timeout 預設60s)。

捕獲到大量異常:Cannot assign requested address

排查過程:

  • 通過 netstat | grep 'WAIT' |wc -l 觀察占用端口狀态,發現占用的端口大于2個。
  • 檢視系統端口回收時間 /sbin/sysctl -a | grep 'fin'
  • 通過調整 作業系統回收端口時間net.ipv4.tcp_fin_timeout=30,問題明顯改善。
【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

二、懷疑okhttp每次請求都是短連結,是以我們review一下代碼

第一我們的請求帶有keep-alive頭

request.newBuilder().addHeader("Connection", "keep-alive")           

第二Okhttp預設會加上Keep-Alive,使用長連結,這段代碼在【BridgeInterceptor.kt】

class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        // 省略部分代碼
        if (userRequest.header("Connection") == null) {
          requestBuilder.header("Connection", "Keep-Alive")
        }
        // 省略部分代碼
    }
}           

三、通過tcpdump+WireShark觀察線上流量,dump出來的請求也證明了之前的代碼沒問題

// 請求頭header:
POST url HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 269
Host: 127.0.0.1:30002
Connection: Keep-Alive
Accept-Encoding: gzip

// 響應頭 header:
HTTP/1.1 200 OK
Content-Length: 109
Content-Type: application/json;charset=UTF-8
Date: Tue, 09 Nov 2021 07:59:43 GMT           

另外通過dump觀察到一個連接配接隻處理了幾個HTTP請求,用戶端主動FIN關閉了連結

【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

從這裡開始我們覺得OkHttp的連接配接池應該有問題。

四、原因解析

首先我們需要了解他的連接配接池

【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

複用連接配接的細節

【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

何時複用連接配接

  • 連接配接池中存在空閑的連接配接(calls引用=0,http1.1最多允許一個calls)
  • 連接配接的是健康的
  • 複用的連接配接和目前的請求的host、port等請求資訊相同

連接配接池的參數

  • 常駐的存活連接配接數量(maxIdleConnections),預設5個
  • 連接配接存活的時間(keepAliveDuration,timeUnit),預設5分鐘

何時觸發釋放連接配接

  • 新的連接配接建立時
  • 一個請求完成的時候
  • 複用RealCall的引用連接配接不健康時

這種故障的問題線上上多服務的場景尤為明細,通過okhttp實作請求,服務的調用是負載均衡的,通常需要請求多個服務,這個也是問題觸發的根據原因。以下通過示意圖解釋為什麼會發生該問題。

【OkHttp源碼分析】解決OkHttp并發瓶頸的問題

A連接配接與F連接配接不符合連接配接複用的條件(host不相同),是以被A連接配接被回收銷毀。

如果這時候再建立一個G連接配接,B連接配接會被銷毀掉,是以這個就是問題所在,我們不能直接使用預設的連接配接池參數

public ConnectionPool() {
	this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {           

其中maxIdleConnections參數的大小應該為:

maxIdleConnections = 請求服務的Host個數 * 請求的服務個數           

例如我的應用需要請求3個服務,3個服務存在6個執行個體(例子host以ip為例)那數量應該為18個;

結尾

至此,我們找到了具體原因,OkHttp衆多構造參數都為我們提供了預設值,一個很小的參數都會導緻我們的服務性能下降。

後面我們将深入分析OkHttp 4.10.0版本連接配接的建立、複用以及銷毀,感興趣的同學歡迎繼續關注!

-END-

繼續閱讀