天天看點

iOS之一個 HTTP 逾時問題論述

一個 HTTP 逾時問題

最近有同僚反映我們的 app 在網絡正常的情況下偶爾會出現請求逾時。

我的第一反應是某個服務挂掉了(因為最近服務端再搞重構),就回報給了服務層。

但是服務層的同僚排查下來發現 api 層并沒有産生異常日志,應該不是服務本身或者依賴的中台服務挂掉了。

定位

想起來 NSURLSession 有個預設的單個Host最大連接配接數,超過之後會進入排隊,可能導緻後續服務逾時。

Objective-C

/* The maximum number of simultanous persistent connections per host */
@property NSInteger HTTPMaximumConnectionsPerHost;      

利用 Xcode 的調試模式來看一下,結果驚掉下巴,随便點了幾下就建立了這麼多連接配接

iOS之一個 HTTP 逾時問題論述

說好的 The default value is 6 in macOS, or 4 in iOS 的呢,難道是Xcode的調試面闆有問題嗎?再次用 Wireshark 抓包确認一下,得到的結果是确實每次請求都會建立一個連接配接(主要展現在端口,TLS 協定)。

下面貼 2 個連接配接的截圖

端口 53929

iOS之一個 HTTP 逾時問題論述

端口 53930

iOS之一個 HTTP 逾時問題論述

這樣肯定是有問題的:

1 HTTP 1.1 預設開啟了 Keep-Alive 屬性(這點在之前的 Charles 抓包中也有證明),為什麼連結沒有複用。

2NSURLSession 的預設單個 Host 最大連接配接數在 iOS 上為 4,為什麼會這麼多。

直接看代碼吧,我們現在項目裡有兩套網絡請求的架構,一套是Objective-C的基于AFNetworking的老代碼,一套是為了向Swift遷移新實作,一番折騰,在以前的基礎庫裡找到了這段看似平平無奇的代碼:

Objective-C

AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@""]];
[manager POST:api parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {...}      

這段代碼的實作裡 ​

​AFHTTPSessionManager​

​ 每次都是新建立出來的,雖然系統預設開啟了 Keep-Alive ,但是 TCP 通道并不會複用,相當于一個披着 HTTP 1.1 外衣的 1.0連接配接,這就回答了第一個疑問。

至于第二個,我重新翻看了官方文檔

This limit is per session, so if you use multiple sessions, your app as a whole may exceed this limit. Additionally, depending on your connection to the Internet, a session may use a lower limit than the one you specify.

意思就是這個最大連接配接數隻是一個 ​

​NRLSession​

​ 的限制。

修複

修複很簡單,我們把上面的 Manager 修改成了屬性,保證了唯一性。完了使用 Xcode 的 Network 工具再看一下。嗯,看起來是複用了。

iOS之一個 HTTP 逾時問題論述

到這裡,解決了一個連接配接不複用的問題。分别觀察了兩個版本的代碼一段時間,修改後問題似乎不再出現了,舊版本的話用 Wireshark 抓到了詳細的逾時的時候的資料包:

iOS之一個 HTTP 逾時問題論述

看上去問題是去服務端握手的時候服務端沒回應,回報給服務端同僚,一開始以為是單個 Linux 主機有個理論上的最大連接配接數限制 65536之類的問題,後來覺得不可能。最終我們猜測是服務端之前設定了單個 api 的最大連接配接數,或者說防火牆那邊會有一段時間内的單個 ip 的最大連接配接數限制造成的。這個問題的根本應該是在服務端,但是用戶端修複了的連接配接方式會緩解這個問題。

驗證

修改代碼上線後,我們觀察了 2 個版本,線上沒有再報類似的問題了。請求逾時的錯誤率也稍有下降。嗯,最棒的是因為複用了連接配接 TCP 慢啟動,TLS 握手都省了,我們請求時間也縮短了。相當于被動的做了優化

修複前

iOS之一個 HTTP 逾時問題論述

修複後

iOS之一個 HTTP 逾時問題論述

代碼真不是随便寫寫,一不小心就埋雷。