1、前言
眾所周之,通常我們開發一個移動端應用,會直接呼叫系統提供的網路請求介面去服務端請求資料,再針對返回的資料進行一些處理,或者使用iOS中的開源AFNetworking/OKHttp這樣的網路庫(Android中可以用HttpURLConnection或者開源的okhttp庫),管理好請求執行緒和佇列,再自動做一些資料解析,就結束了。
但對於追求使用者體驗的應用來說,還會針對行動網路的特性做進一步優化,包括:
1)速度優化:網路請求的速度怎樣能進一步提升?
2)弱網適應:移動端網路環境隨時變化,經常出現網路連線很不穩定可用性差的情況,怎樣在這種情況下最大限度最快地成功請求?
3)安全保障:怎樣防止被第三方竊聽/篡改或冒充,防止運營商劫持,同時又不影響效能?
對基於瀏覽器的前端開發來說,網路這塊能做的事情很少,但對於原生的移動端應用來說(本文中說的原生主要指iOS和Android應用),整個網路請求過程是自由控制的,可以做很多事情。很多大型 APP 都針對這三個問題做了很多網路層的優化,一些新的網路層協議像 HTTP2 / QUIC 也是在這些方面進行了不少優化。在此請跟著我的文字,邊學習邊整理,總結一下當今主流的移動端網路短連線常見優化手段,希望能給您帶來啟發。
本文整理的有關內容,對於移動端即時通訊IM應用來說,同樣具有啟發意義,因為現今主流的移動端IM資料通訊總結下來無外乎就是長連線+短連線的方式,則短連線的優化在某些場景下對於移動端IM來說可能顯示的更為特出。在這方面,微信做的比較徹底和極端,幾乎再造了一套針對移動端IM的網路層框架(詳見:《如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》)。
(本文同步釋出於:www.52im.net/thread-1413…)
2、相關文章
1)關於網路通訊的基礎文章:
如果您對網路通訊知識瞭解甚少,建議閱讀《網路程式設計懶人入門系列文章》,更高深的網路通訊文章可以閱讀《不為人知的網路程式設計系列文章》。
2)涉及移動端網路特性的文章:
《騰訊原創分享(一):如何大幅提升行動網路下手機QQ的圖片傳輸速度和成功率》
《騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(上篇)》
3、關於作者
本文原始內容來自JSPatch開源工程作者bang的技術分享,他的部落格是:blog.cnbang.net/about/,本次內容有優化和完善,感謝原作者的無私分享。
4、請求速度的優化
正常一條網路請求需要經過的流程是這樣:
1)DNS 解析,請求DNS伺服器,獲取域名對應的 IP 地址;
2)與服務端建立連線,包括 tcp 三次握手,安全協議同步流程;
3)連線建立完成,傳送和接收資料,解碼資料。
這裡有明顯的三個優化點:
1)直接使用 IP 地址,去除 DNS 解析步驟;
2)不要每次請求都重新建立連線,複用連線或一直使用同一條連線(長連線);
3)壓縮資料,減小傳輸的資料大小。
逐條來看能做什麼。
4.1 DNS優化
DNS 完整的解析流程很長,會先從本地系統快取取,若沒有就到最近的 DNS 伺服器取,若沒有再到主域名伺服器取,每一層都有快取,但為了域名解析的實時性,每一層快取都有過期時間。
上面這種 DNS 解析機制有幾個缺點:
1)快取時間設定得長,域名更新不及時,設定得短,大量 DNS 解析請求影響請求速度;
2)域名劫持,容易被中間人攻擊,或被運營商劫持,把域名解析到第三方 IP 地址,據統計劫持率會達到7%;
3)DNS 解析過程不受控制,無法保證解析到最快的IP;
4)一次請求只能解析一個域名。
為了解決這些問題,就有了 HTTPDNS,原理很簡單,就是自己做域名解析的工作,通過 HTTP 請求後臺去拿到域名對應的 IP 地址,直接解決上述所有問題。
自已實現HTTPDNS的好處總結就是:
1)域名解析與請求分離,所有請求都直接用IP地址,無需 DNS 解析,APP 定時請求 HTTPDNS 伺服器更新IP地址即可;
2)通過簽名等方式,保證 HTTPDNS 請求的安全,避免被劫持;
3)DNS 解析由自己控制,可以確保根據使用者所在地返回就近的 IP 地址,或根據客戶端測速結果使用速度最快的 IP;
4)一次請求可以解析多個域名。
其餘細節就不多說了,HTTPDNS 優點這麼多,幾乎成為中大型 APP 的標配。至此解決了第一個問題 — DNS 解析耗時的問題,順便把一部分安全問題 — DNS 劫持也解決了。
關於移動端網路中DNS的問題,《談談移動端 IM 開發中登入請求的優化》一文中也有提到,僅供參考。
4.2 連線的優化
第二個問題,連線建立耗時的問題,這裡主要的優化思路是複用連線,不用每次請求都重新建立連線,如何更有效率地複用連線,可以說是網路請求速度優化裡最主要的點了,並且這裡的優化仍在演進過程中,值得了解下。
▼【keep-alive】:
HTTP 協議裡有個 keep-alive,HTTP1.1預設開啟,一定程度上緩解了每次請求都要進行TCP三次握手建立連線的耗時。原理是請求完成後不立即釋放連線,而是放入連線池中,若這時有另一個請求要發出,請求的域名和埠是一樣的,就直接拿出連線池中的連線進行傳送和接收資料,少了建立連線的耗時。
實際上現在無論是客戶端還是瀏覽器都預設開啟了keep-alive,對同個域名不會再有每發一個請求就進行一次建連的情況,純短連線已經不存在了。但有個問題,就是這個 keep-alive 的連線一次只能傳送接收一個請求,在上一個請求處理完成之前,無法接受新的請求。若同時發起多個請求,就有兩種情況:
若序列傳送請求,可以一直複用一個連線,但速度很慢,每個請求都要等待上個請求完成再進行傳送。
若並行傳送這些請求,那麼首次每個請求都要進行tcp三次握手建立新的連線,雖然第二次可以複用連線池裡這堆連線,但若連線池裡保持的連線過多,對服務端資源產生較大浪費,若限制了保持的連線數,並行請求裡超出的連線仍每次要建連。
對這個問題,新一代協議 HTTP2 提出了多路複用去解決。
PS:關於理解TCP的3次握手原理,以下文章可能對您會有幫助
▼【多路複用】:
HTTP2 的多路複用機制一樣是複用連線,但它複用的這條連線支援同時處理多條請求,所有請求都可以併發在這條連線上進行,也就解決了上面說的併發請求需要建立多條連線帶來的問題。
網路上有張圖可以較形象地表現這個過程:
HTTP1.1的協議裡,在一個連線裡傳送資料都是序列順序傳送的,必須等上一個請求全部處理完後,下一個請求才能進行處理,導致這些請求期間這條連線並不是滿頻寬傳輸的,即使是HTTP1.1的pipelining可以同時傳送多個request,但response仍是按請求的順序序列返回,只要其中一個請求的response稍微大一點或發生錯誤,就會阻塞住後面的請求。
HTTP2 這裡的多路複用協議解決了這些問題,它把在連線裡傳輸的資料都封裝成一個個stream,每個stream都有標識,stream的傳送和接收可以是亂序的,不依賴順序,也就不會有阻塞的問題,接收端可以根據stream的標識去區分屬於哪個請求,再進行資料拼接,得到最終資料。
解釋下多路複用這個詞,多路可以認為是多個連線,多個操作,複用就是字面上的意思,複用一條連線或一個執行緒。HTTP2這裡是連線的多路複用,網路相關的還有一個I/O的多路複用(select/epoll),指通過事件驅動的方式讓多個網路請求返回的資料在同一條執行緒裡完成讀寫。
移動客戶端來說,iOS 9 以上 NSURLSession 已原生支援 HTTP2,只要服務端也支援就可以直接使用,Android 的開源網路庫 okhttp3以上版本也支援了 HTTP2,國內一些大型 APP 會自建網路層,支援 HTTP2 的多路複用,避免系統的限制以及根據自身業務需要增加一些特性,例如微信的開源網路庫 mars(詳見《如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》),做到一條長連線處理微信上的大部分請求,多路複用的特性上基本跟 HTTP2 一致。
▼【TCP隊頭阻塞】:
HTTP2 的多路複用看起來是完美的解決方案,但還有個問題,就是隊頭阻塞,這是受限於 TCP 協議,TCP 協議為了保證資料的可靠性,若傳輸過程中一個 TCP 包丟失,會等待這個包重傳後,才會處理後續的包。HTTP2的多路複用讓所有請求都在同一條連線進行,中間有一個包丟失,就會阻塞等待重傳,所有請求也就被阻塞了。
對於這個問題不改變 TCP 協議就無法優化,但 TCP 協議依賴作業系統實現以及部分硬體的定製,改進緩慢,於是 GOOGLE 提出 QUIC 協議(詳見《技術掃盲:新一代基於UDP的低延時網路傳輸層協議——QUIC詳解》),相當於在 UDP 協議之上再定義一套可靠傳輸協議,解決 TCP 的一些缺陷,包括隊頭阻塞。具體解決原理網上資料較多,可以看看。
QUIC 處於起步階段,少有客戶端接入,QUIC 協議相對於 HTTP2 最大的優勢是對TCP隊頭阻塞的解決,其他的像安全握手 0RTT / 證照壓縮等優化 TLS1.3 已跟進,可以用於 HTTP2,並不是獨有特性。TCP 隊頭阻塞在 HTTP2 上對效能的影響有多大,在速度上 QUIC 能帶來多大提升待研究(關於這一點可以看看騰訊的QUIC技術實踐《讓網際網路更快:新一代QUIC協議在騰訊的技術實踐分享》)。
PS:關於新一代QUIC協議的更多文章請見
4.3 資料壓縮優化
第三個問題,傳輸資料大小的問題。資料對請求速度的影響分兩方面,一是壓縮率,二是解壓序列化反序列化的速度。目前最流行的兩種資料格式是 json 和 protobuf,json 是字串,protobuf 是二進位制,即使用各種壓縮演算法壓縮後,protobuf 仍會比 json 小,資料量上 protobuf 有優勢,序列化速度 protobuf 也有一些優勢,這兩者的對比就不細說了。(關於protobuf的原理,詳見《Protobuf通訊協議詳解:程式碼演示、詳細原理介紹等》、《全方位評測:Protobuf效能到底有沒有比JSON快5倍?》、《強列建議將Protobuf作為你的即時通訊應用資料傳輸格式》)
壓縮演算法多種多樣,也在不斷演進,最新出的 Brotli 和Z-standard實現了更高的壓縮率,Z-standard 可以根據業務資料樣本訓練出適合的字典,進一步提高壓縮率,目前壓縮率表現最好的演算法。
除了傳輸的 body 資料,每個請求 HTTP 協議頭的資料也是不可忽視,HTTP2 裡對 HTTP 協議頭也進行了壓縮,HTTP 頭大多是重複資料,固定的欄位如 method 可以用靜態字典,不固定但多個請求重複的欄位例如 cookie 用動態字典,可以達到非常高的壓縮率,這裡有詳細介紹《HTTP/2 頭部壓縮技術介紹》(該作者針對HTTP2有很多研究,更多HTTP2文章可見作者的HTTP2技術專題,方便進行深入學習)。
通過 HTTPDNS,連線多路複用,更好的資料壓縮演算法,可以把網路請求的速度優化到較不錯的程度了,接下來再看看弱網和安全上可以做的事情。
5、針對移動弱網的優化
手機無線網路環境不穩定,針對弱網的優化,微信有較多實踐和分享,包括:
1)提升連線成功率:
複合連線,建立連線時,階梯式併發連線,其中一條連通後其他連線都關閉。這個方案結合序列和併發的優勢,提高弱網下的連線成功率,同時又不會增加伺服器資源消耗,見下圖
2)制定最合適的超時時間:
對總讀寫超時(從請求到響應的超時)、首包超時、包包超時(兩個資料段之間的超時)時間制定不同的計算方案,加快對超時的判斷,減少等待時間,儘早重試。這裡的超時時間還可以根據網路狀態動態設定;
3)調優TCP引數,使用TCP優化演算法:
對服務端的TCP協議引數進行調優,以及開啟各種優化演算法,使得適合業務特性和移動端網路環境,包括RTO初始值,混合慢啟動,TLP,F-RTO等。
針對弱網的這些細緻優化未成為標準,系統網路庫沒有內建,不過前兩個客戶端優化微信的開源網路庫 mars 有實現,若有需要可以使用(詳見《如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》)。
6、安全方面
標準協議 TLS 保證了網路傳輸的安全,前身是 SSL,不斷在演進,目前最新是 TLS1.3。常見的 HTTPS 就是 HTTP 協議加上 TLS 安全協議。
安全協議概括性地說解決兩個問題:
1)保證安全;
2) 降低加密成本。
在保證安全上:
1)使用加密演算法組合對傳輸資料加密,避免被竊聽和篡改;
2)認證對方身份,避免被第三方冒充;
3)加密演算法保持靈活可更新,防止定死演算法被破解後無法更換,禁用已被破解的演算法。
降低加密成本上:
1)用對稱加密演算法加密傳輸資料,解決非對稱加密演算法的效能低以及長度限制問題;
2)快取安全協議握手後的金鑰等資料,加快第二次建連的速度;
3)加快握手過程:2RTT-> 0RTT。加快握手的思路,就是原本客戶端和服務端需要協商使用什麼演算法後才能加密傳送資料,變成通過內建的公鑰和預設的演算法,在握手的同時就把資料發出去,也就是不需要等待握手就開始傳送資料,達到0RTT。
這些點涉及的細節非常多,對 TLS 的介紹有一篇雄文,說得很詳細,在此推薦:《TLS協議分析 與 現代加密通訊協議設計》(JackJiang注:這篇文章長的驚人,希望你能耐心把它看完 ^_^)。
下面這幾篇是有關移動端通訊安全的基礎文章,比上面那篇要容易理解:
《傳輸層安全協議SSL/TLS的Java平臺實現簡介和Demo演示》
《微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》
目前基本主流都支援 TLS1.2,iOS 網路庫預設使用 TLS1.2,Android4.4 以上支援 1.2。TLS1.3 iOS 還處於測試階段,Android 未查到訊息。對於普通 APP,只要正確配置證照,TLS1.2 已經能保證傳輸安全,只是在建連速度上會有所損耗,有一些大型 APP 像微信就自行實現了 TLS1.3 的部分協議,早一步全平臺支援(微信團隊專門分享過關於TLS1.3的實踐文章,詳見《微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》)。
7、寫在最後
移動端網路優化這個話題非常龐大,本文只是在學習過程中從優化思路上列舉了目前業界常見的優化點,還有很多細節很多更深入的優化沒涉及到,網路層實踐開發經驗不足,若有錯誤歡迎指出。
(本文同步釋出於:www.52im.net/thread-1413…)