OkHttp使用踩坑記錄總結(一):OkHttpClient單例和長連接Connection Keep-Alive

說明

在項目中對第三方服務的調用,使用了OkHttp進行http請求,這當中踩了許多坑。本篇博文將對OkHttp使用過程遇到的問題進行總結記錄。

正文

OkHttpClient 單例

在剛開始使用時,沒有將OkHttpClient單例化,造成的後果就是服務器OOM異常,can’t create native thread…

爲什麼會出現OOM,提示無法創建本地線程?
通過查看資料文檔發現,每個client對象都有自己的線程池和連接池,如果爲每個請求都創建一個client對象,自然會出現內存溢出。所以官方建議OkHttpClient應該單例化,重用連接和線程能降低延遲和減少內存消耗

OkHttpClients should be shared
OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.

官方介紹了三種創建client的方式:

  1. new OkHttpClient()
    該方式將創建一個使用默認設置的client單例對象。
  2. new OkHttpClient.Builder()
    該方式允許自定義配置自己的單例client對象。配置connectionTimeout, readTimeout, writeTimeout等參數。
 okHttpClient = new OkHttpClient.Builder()
                        .connectTimeout(50L, TimeUnit.SECONDS)
                        .readTimeout(60L, TimeUnit.SECONDS)
                        .build();
  1. okHttpclient.newBuilder()
    該方式通過已經存在的client對象,創建特殊需要的client對象。如 我們通過上個方法創建了自定義配置的單例client對象,但是針對某些場景需要調整某些參數,那麼就需要使用該方法創建定製的client。新client對象與舊client對象共享連接池,線程池和其他配置

You can customize a shared OkHttpClient instance with newBuilder(). This builds a client that shares the same connection pool, thread pools, and configuration. Use the builder methods to configure the derived client for a specific purpose.

OkHttpClient myClient = okHttpClient.newBuilder()
                        .readTimeout(80L, TimeUnit.SECONDS).build();

通過該方式解決了OkHttpClient針對不同請求設置不同參數的問題。在這篇文章中HTTP客戶端連接,選擇HttpClient還是OkHttp?作者對OkHttpClient和HttpClient作了性能比較,可以看出OkHttpClient的性能要好於HttpClient。 其中在非單例比較時,就單純的建立連接耗時,OkHttpClient也優於HttpClient。

長連接 Connection: keep-alive

之所以使用OkHttp是因爲它能高效率,高吞吐量的進行http請求,優於HttpClient。它的高效率,官方給出了四個原因:

  1. 支持HTTP/2,允許相同地址請求可以共享一個socket連接。
  2. 在HTTP/2不可用時,連接池可以減少請求延遲。
  3. 支持透明的GZIP壓縮響應
  4. 支持響應緩存,避免重複請求。

以上可以看出,OkHttp支持HTTP/1.1和HTTP/2協議。通過這篇文章一文讀懂 HTTP/1HTTP/2HTTP/3,我們可以瞭解下HTTP協議不同版本的特點。

通過了解可以知道,HTTP/1.1和HTTP/2的http請求都是建立在TCP連接基礎之上的,所以他們的性能極大的依賴於TCP連接的配置。通過我之前的文章計算機網絡,可以對TCP進行了解。

每個TCP連接都會進行三次握手,並且因爲TCP的擁塞控制使用的滑動窗口和慢開始算法導致網絡帶寬利用率不高。所以,在HTTP/2不可用時,OkHttp使用了連接池,避免爲每個請求都創建連接

OkHttp在創建連接時,默認創建長連接,這是因爲在HTTP1.1中Connection: keep-alive是默認設置的,而HTTP1.0是Connection: close, 這表示每次請求完都會關閉連接,並且OkHttp不支持HTTP1.0。所以,OkHttp使用連接池複用這些長連接。

複用長連接,避免了創建TCP連接的三次握手和慢開始,我們簡單瞭解下HTTP請求頭中Connection和Keep-Alive。

Connection作爲請求頭的通用參數,其控制了請求連接在當前事務完成後是否繼續保持。如果其值被設置爲keep-alive, 則表示該連接是長連接,允許之後的相同地址的請求複用。還可以設置爲close,每次請求結束後關閉連接。但是該參數在HTTP/2中不再使用。

Keep-Alive同樣作爲請求頭通用參數,其允許發送方設置連接的使用規則。參數timeout設置了空閒連接的最小存活時間,單位爲秒(s);參數max設置了一個連接最多可以進行多少個請求。

然而在實際使用中,服務器會時不時拋出java.net.SocketException: Connection reset異常。通過這篇文章 Connection reset原因分析和解決方案,可以知道在使用OkHttp時,客戶端和服務端連接應該保持一致,要麼都是長連接,要麼都是短連接。對此,服務端我們無法設置,只能是設置Connection爲close。

並且在查資料時,碰見了兩個問題:

  1. OkHttp默認是使用長連接進行請求,那麼如果要發送非長連接的請求如何實現?
    我們可以在請求時設置請求頭參數Connection參數爲close。
  2. OkHttp複用連接,但是當重新請求時,之前獲得的session丟失,如何處理? 問題詳情
    這個問題,在服務端獲取session後會在影響頭的cookie中設置sesisonID,所以當複用連接進行請求時,需要從之前的請求頭中獲取sessionId,並設置到新請求的Cookie參數中。
Headers headers = firstResponse.headers();
requestBuilder = requestBuilder.addHeader("Cookie", headers.get("Set-Cookie"));

通過文檔,我們可以瞭解OkHttp是如何建立連接的:

  1. 當進行請求時,OkHttp使用請求的URL和配置創建的OkHttpClient對象去創建一個地址(Address),該地址說明了如何連接服務端。
  2. 根據創建的地址嘗試從連接池複用連接。
  3. 如果連接池中沒有連接,則選擇一個路由(Rote)創建連接。這通常意味着要發送一個DNS請求獲取服務端的IP地址,如果需要還可以選擇TLS版本和代理服務器。
  4. 如果是一個新的路由,則會通過socket連接,TLS管道或者是TLS連接與與服務端進行連接。
  5. 最後發送HTTP請求和讀取響應。

如果服務器地址不可達,OkHttp則會重新選擇路由進行重試恢復連接。並且當響應被接收後,連接會被放回連接池等待複用,當連接空閒時長超過配置值時,則會被關閉。

通過源碼,可以看到在創建OkHttpClient對象時,其通過內部靜態類Builder構造器進行創建配置。在Builder類中新建了ConnectionPool對象,但該連接池沒有配置連接池的大小,而是設置了最大空閒連接數maxidleConnection和存活時長keepAliveDuration

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

那OkHttp是如何控制連接池的大小呢?在此之前,我們先了解下OkHttp的同步請求和異步請求。

請看下篇文章《OkHttp使用踩坑記錄總結(二):OkHttp同步異步請求和連接池線程池》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章