本文由得物技術厲飛雨分享,原題“得物App弱網診斷探索之路”,下文進行了排版和內容優化。
1、引言
隨著得物用戶規模和業務復雜度不斷提升,端上網絡體驗優化已逐步進入深水區。為了更好地保障處于弱網狀態下得物App用戶的使用體驗,我們在已有的網絡體驗大盤、網絡診斷工具的基礎上研發了弱網診斷能力。該工具能夠高效實時診斷用戶真實網絡環境,同時給出精確網絡質量分級,為后續App各業務場景進行針對性優化做好基礎建設保障。
本文將基于得物自研的移動端弱網診斷工具的開發過程,盡可能全面地為你總結和分享它的具體技術實踐,希望帶給你啟發。
* 推薦閱讀:淘寶技術團隊分享的《淘寶移動端統一網絡庫的架構演進和弱網優化技術實踐》,可一并閱讀。
技術交流:
2、網絡性能概念
一些網絡性能指標:
- 1)速率:指傳送數據的速率。速率是計算機網絡中最重要的性能指標,單位是b/s(比特每秒,也寫作bps ,即bit per second )。速率較高時可寫作kbps,mbps。
- 2)帶寬:帶寬用來表示網絡的通信線路傳送數據的能力,表示在單位時間內從網絡中的某一點到另一點所能通過的“最高速率”。
- 3)吞吐量(throughput):表示在單位時間內通過某網絡的數據量。吞吐量常用于對網絡的一種測量。吞吐量受網絡帶寬或者速率限制。例如,對于一個100Mb/s的網絡,其額定速率是100Mb/s,那么該數值是此網絡的吞吐量的上限,其吞吐量可能只有70Mb/s。
- 4)時延:指數據從網絡上的一端傳輸到另一端所需要的時間。
- 5)往返時間RTT(Round-Trip Time):表示從發送方發送數據開始,到發送方收到接收方的確認,總共經歷的時間。
- 6)HttpRTT:指從客戶端請求的第一個字節開始發送到收到第一個數據包響應的時間差。這個時間包含3個部分,客戶端發送數據到服務器耗時、服務器處理耗時、服務器響應數據到客戶端耗時。
弱網診斷觀察的指標(弱網診斷根據HttpRTT和吞吐量來觀察用戶網絡環境):
- 1)HttpRTT:在不考慮服務器處理耗時的情況下,能夠體現用戶請求被處理的真實時延。
- 2)吞吐量(throughput):用戶的額定速率能被系統提供的API獲取到,然而其僅能表示設備能夠提供的最大速率(一般很大),卻不是真實速率,而測量真實的吞吐量更能體現出用戶當前真實網絡環境。
3、整體架構
本次實現的是被動弱網診斷,也就是不主動發起探測請求,被動采集App內的全部網絡請求,再根據一定在策略計算出用戶網絡環境。
相對于主動探測,被動探測不會浪費用戶資源。
尤其是在吞吐量計算方面,主動探測不僅會消耗用戶流量,還可能會對正在進行中的用戶網絡請求產生影響。而且當用戶網絡環境不佳時,負向影響更加嚴重。
以下為被動網絡診斷的整體架構圖:
4、采集層實現1:HttpRTT采集
4.1概述
HttpRTT的采集比較簡單,各端根據自身Http協議棧的實現獲取到從寫入requestHeader開始,到收取responseHeader的時間差即可。
對于Android:我們通過OkHttp完成Http請求,通過向OkHttp注冊網絡監聽即可實現。
需要說明的是:在不修改源碼的情況下,Android無法獲取到收到第一個響應數據包的時間,只能監聽到Header讀取完成,這會有些許誤差,但實測下來可以忽略。
對于其他客戶端內通過自行實現Http協議棧發起的網絡請求(如PCDN),我們通過向其注冊特定的監聽回調,也能獲取到HttpRTT。
4.2采集樣本過濾
HttpRTT包含了服務器處理時間,而當服務器處理時間過長或過低時對其他普遍意義上的Http請求參考價值相對較低。
因此我們需要排除這些數據:
- 1)localhost:如果向localHost發起請求,且響應的僅是緩存的數據,那么其HttpRTT時延明顯接近于0;
- 2)PUT請求:由于PUT請求傳輸的數據量一般較大,其HttpRTT明顯高于其他;
- 3)其他明顯偏離的數據。
5、采集層實現2:吞吐量(throughput)采集
5.1概述
throughput(bit/s , bps) = 單位時間內通過的數據量(bit) / 單位時間(s)
吞吐量的采集相對要復雜得多,吞吐量采集目的是獲取用戶所能使用的最大的數據率(或帶寬),因而其需要在設備上恰好有大量數據正在傳輸時采集。
對于主動探測來說,在無其他請求干擾的情況下,主動發起一個大數據量CDN下載請求即可快速測量出吞吐量。而被動探測則需要想辦法預測或檢測到大量數據傳輸的時刻,并適時計算吞吐量。
5.2時間窗口
怎樣選取計算吞吐量的時間窗口是計算吞吐量準確性的關鍵。
這個窗口要恰好在大量數據正在傳輸時,不能早也不能晚:
- 1)如果提前開啟了時間窗口,那么同樣的大小的數據通過,由于分母增大會導致計算出的數值偏低;
- 2)如果數據傳輸完成后,稍晚關閉時間窗口,那么同樣會由于分母增大而導致計算出的數值偏低。
我們知道Http請求或多或少會有上行/下行數據,但由于服務器處理耗時長短的不確定性(不能算在分母里),單個Http請求測速時并不可靠。
而多個Http正在并發請求進行中的時候,其請求的流量會疊加,單個請求的服務器耗時會被其他Http請求覆蓋,此時采集吞吐量會是個好的選擇。因此,我們可以在監聽到Http并發請求數量達到5個以上時采集吞吐量。
時間串口的選擇(忽略建連耗時):

可以看出,當并發較多時,服務器耗時會被覆蓋,每個時間窗口內存在約4個Http請求的響應數據。
我們始終監聽App內的全部Http請求,當監控到有5個及以上的網絡請求尚未完成時,也就是Http并發5個以上,開啟時間窗口;當時間窗口已開啟且任意一個請求結束時,我們結束當前時間窗口。
需要注意的是:當我們結束一個時間窗口的時候,需要立刻檢測當前并發是否5個以上,而不是等到新的請求到來時,這樣能避免類似上圖的采樣機會被浪費掉,而采樣成功的樣本數越多,越有利于最終結論的準確性(后面策略層會講原因)。
可行性:我們的App內能滿足5個并發以上嗎?
當然可以。通過觀察線下測試和線上數據分析,我們App內的并發數能夠滿足吞吐量采集的必要條件。舉個例子,進入商詳一次的并發量就能滿足。
用戶A啟動90秒內Http并發數量(線上數據):

5.3數據量
分母(單位時間)的問題解決了,分子(單位時間內通過的數據量)如何取值呢?
思路是:
- 1)思路1:當前時間窗口內并行的Http通過的Reponse數據量;
- 2)思路2:設備內所傳輸的數據量;
- 3)思路3:當前網卡傳輸的數據量。
先說思路1:這個實現上需要我們在時間窗口開始時記錄全部Http請求已傳輸的字節數,時間窗口關閉時再記錄一次,然后把當前并行的Http請求時間窗口內的數據量全部累加起來。這意為著我們要時刻監控每一個Http請求中每個字節的讀取,成本太高了。另一方面,如果有其他非Http請求(或者我們App之外的請求)也在進行,我們僅測算App內Http請求的吞吐量顯然是偏低的。因此,思路1不合適。
再說思路2:相對合適。我們要測算的就是設備的吞吐量。因為即使是App外的流量,其也會有結束的時候,總能為我們所用(除非用戶對我們App單獨做帶寬限制)。
相對于獲取設備內的全部流量,獲取網卡的流量則更為合適:
- 1)原因1:部分系統已經支持WIFI與蜂窩并行請求,如果當前使用的是WIFI,將App外的蜂窩流量測算進去會導致偏差。
- 2)原因2:網絡切換時,處于舊網絡上的請求不會立即釋放,而新的請求會發生在新的網絡上,此時設備內的通過的流量是多網卡的累積。
需要注意的是系統API返回的是字節數(byte),而我們計算的是bit,因此計算吞吐量時需要進行換算。
5.4臟數據過濾
前面講到“并發5個時,每個時間窗口內可以采集到約4個Http請求的響應數據”,然而運氣并不會始終這樣好。
窗口掛起:

如上圖所示:時間窗口1內僅兩個有效的response,時間窗口2內僅一個有效的response,其計算出的吞吐量必然是偏低的。因此,臟數據過濾就顯得十分重要。
1)小數據量過濾:
時間窗口內通過的流量小于32KB時,不會產生準確的速率,我們直接忽略這次采樣。要知道,一個圖片的數據量都可能會超過32KB。
2)低利用率過濾:
低利用率是指由于數據需求較小,導致當前速率遠未達到最大吞吐率的情況。如上圖時間窗口1,對于一個未被充分利用的網絡,我至少希望一個HttpRTT的時間內接收到的數量大于15KB。
// 窗口是否掛起
fun isHangingWindow(bitsRx: Long, duration: Long): Boolean {
val kCwndSizeBits = 10 * 1.5 * 1000 * 8
val multiplier = 1
val httpRTT = ??? //由Http RTT模塊計算
val bitsReceivedOverOneHttpRtt = bitsRx * httpRTT / duration
return bitsReceivedOverOneHttpRtt < kCwndSizeBits * multiplier
}
為什么是15KB ?如果TCP連接剛剛建立,由于Linux系統的默認設置,客戶端能夠同時發送10個數據段,每個數據段時1460字節,合計也就是15KB。
6、策略層實現
到這里,我們已經能采集到很多HttpRTT樣本、throughput樣本了,現在我們要考慮下怎么將這些樣本綜成計算出一個可以代表設備普遍意義上的HttpRTT、throughput,然后再歸類出設備網絡類型(慢的網絡、一般的網絡、快的網絡、很快的網絡 ...)。
6.1中位數
首選是選取什么樣的策略將眾多的HttpRTT樣本計算出最終值。
可選方式如下:
中位數相對于平均數等其他統計量來說,更適合處理包含極端值、偏態分布或受到干擾的數據集。
6.2時間權重
用戶網絡質量可能隨時間而發生變化,最新的樣本數據更接近于當前網絡環境。我們對樣本數據施加時間權重,樣本數據每60秒降低一半權重,那么越新的樣本權重越高。
時間權重:
60秒半衰期下不同時間差下的權重:
6.3信號強度權重
同樣的,信號強度也可能會隨用戶移動位置而發生變更,不同信號強度下的網絡質量也會不同。
我們將用戶信號強度劃分為4個等級,再根據信號強度等級的變化施加不同的權重。那么,樣本數據生成時的信號強度與當前信號強度越是接近,其樣本權重就越高。
信號權重:

目前僅Android且網絡環境為WIFI會計算信號強度權重。
6.4加權中位數
最終,將全部樣本以加權中位數的方式,計算HttpRTT、throughput。而最終權重由時間權重與信號強度權重結合得出。
綜合權重:

6.5計算與緩存
如圖4所示,應用網絡請求并發量能達到20+,而啟動1分鐘內總Http請求數到達了450,平均每秒約8個請求。而我們線下實測每次計算耗時1~10ms,樣本總數越高時耗時也越高。因此,我們要考慮下如何降低計算頻率。
1)適時計算:
調用方總是期望在請求API時立刻能夠拿到最新的、最準確的結果。
一個簡單的方式是業務請求時將全部樣本重新計算一遍,一開始我們也確實是這么設計的,然而每次計算耗時1~10ms, 這個對調用方來說是顯然是無法接受。
那么,我們真的需要在調用時將全部樣本重算一遍嗎?
首先來說,只有新的計算結果和舊的計算結果有差異時,我們才需要重新計算。我們的計算策略是加權中位數,其計算來源是樣本總數、樣本權重,而樣本權重受時間、信號強度影響。
具體是:
- 1)對于樣本數、信號強度:其變化時必然會引起最終結果的變化;
- 2)對于時間,通過單個請求的時間權重公式(圖4),我們可以推導出多個樣本時,單個樣本的權重不會隨當前時間發生變化(圖8),也就是我們無需考慮時間流逝對時間權重的計算影響。
單個樣本的時間權重:
那么,我們只需要在樣本數、信號強度發生變化時重新計算,然后將計算結果緩存下來。
2)計算條件:
也并非只要有新樣本生成我們就要重新計算一遍,那樣計算頻率太高了。
總的來說,當有新樣本到來時,重新計算需滿足以下任意一個條件:
- 1)從未計算出結果;
- 2)網絡類型發生變更;
- 3)信號強度發生變更;
- 4)當前時間較上次計算大于10秒;
- 5)Http樣本數較上次計算多出50%;
- 6)throughput樣本數較上次計算多出50%。
HttpRTT樣本+throughput樣本合計增加20以上。
3)網絡變更監聽:
對于HttpRTT樣本,throughput樣本,我們各保留最新的300個。當網絡發生變更時我們會清除全部,因為舊的船票不能登上新船。
7、接口層實現
現在我們有了具體的HttpRTT、throughput,然而大部分業務并不需要這些數字,他們只想知道當前網絡怎么樣,快不快,有多快。基于此,我們根據HttpRTT、throughput將用戶網絡劃分為5個類型,通過接口提供給上層。

這里throughput的單位是kbps,如果換算成常見的KB/s,需要除以8。
需要說明的是:這里的TYPE_2G并不是指我們手機信號里所展示的2G,而是基于被動探測對用戶網絡環境劃分的結果。
即使用戶連接的是5G網絡,當其因信號不好等其他因素導致RTT較高或throughput較低時,也會被劃分到TYPE_2G。換句話說,這里用TYPE_2G就是表明網絡很慢,就和很多年前的2G一樣慢。
此外,由于網絡類型的計算是在網絡請求完成時進行的異步計算,上層通過接口讀取的始終是緩存,所以無需考慮調用時的性能問題。
8、 閾值定義
網絡類型劃分里有4個關鍵數值:272ms、511ms、400kpbs、1600kpbs,這些數組是怎么定義出來的呢?

1)線下測試:
首選通過弱網工具判斷我們的HttpRTT、throughput估算的是否準確。通過Round-trip latancy(ms) 增加延遲觀察HttpRTT是否同步改變,通過限制Bandwidth(kbps)觀察throughput數值是否與之一致。
charless弱網模擬:

2)線上驗證:
網絡監控中攜帶當前網絡類型,統計線上數據觀察TYPE_2G、TYPE_3G、TYPE_4G網絡請求耗時表現。
3)反復實驗:
通過調整關鍵參數找到最有區分度的閾值。如緩存的樣本總數越高就表明參照了越多的老數據、時間權重半衰期越長老數據的權重就越高、窗口掛起判定中multiplier越高樣本數越少結果越準確……
通過實驗數據調整我們預埋的參數,能使判定更加準確。
9、性能指標
9.1HttpRTT 數值計算
用戶B 全部網絡請求的首字節耗時與nqeHttpRTT:

上圖是線上某用戶B的首字節耗時(HttpRTT)與最終計算出的nqeHttpRTT。單次網絡請求耗時會有波動,但nqeHttpRTT維持準確與穩定。
注意:
- 1)nqeHttpRTT指最終估算出的HttpRTT;
- 2)為優化顯示,圖中縱軸以xx ms為最高值,實際上部分請求遠高于此;
- 3)nqeHttpRTT未伴隨Http請求出立刻出現是因為診斷模塊啟動時機較晚,事實上nqeHttpRTT得計算最低只需要5個完成Head響應的Http請求。
9.2吞吐量(throughput)數值
用戶B 全部網絡請求的并發數與吞吐率:

上圖是線上某用戶B的并發數與吞吐率計算結果。并發5個以上時會采集出一個以上吞吐率樣本,可以看到我們App內的并發數能夠滿足我們對吞吐率的采集需求,而估算出的吞吐率(nqeKbps)數值穩定。
9.3準確性判定
大部分調用方并不關注具體的HttpRTT,吞吐量數值,而只關注我們劃分出的網絡類型。劃分網絡類型的準確性自然是調用方關注的重點。
那么,如何衡量我們的準確性呢?換句話說,我們判斷為弱網,他真的是弱網嗎?
一個簡單的方式是請求耗時大于500ms算作實際弱網,但是業務接口的耗時與CDN的耗時會有明顯差異,500ms明顯不能作為CDN耗時的弱網衡量標準。
如果我們以單個接口耗時500ms作為實際弱網呢?也會存在一些問題,比如西藏的用戶與杭州(機房在這里)的用戶在網絡延遲上明顯有巨大差異(物理距離決定的)。即使網絡極佳的西藏用戶,業務接口的耗時也會高于網絡一般的杭州用戶。但是網絡極佳的西藏用戶訪問CDN時,其耗時又要優于杭州的網絡一般的用戶。
如果以某省份單接口耗時大于500ms呢?且不說可能以偏概全,就目前而言,同一省份的IPv4和IPv6的連接的機房都可能會不一樣(有杭州,也有北京)。
.....
1)目前的準確性判定:
我們以實際耗時比50分位網絡傳播耗時慢一倍為實際弱網(判斷準確),比50分位耗時小為實際正常(誤判或其他)。
網絡耗時階段:


2)怎么定義實際弱網?
實際弱網(請求慢)=(request耗時+response耗時)*2+服務器處理+其他=50分位總耗時+(request耗時+response耗時)
實際正常(請求快)=(request耗時+response耗時)+服務器處理+其他=50分位總耗時
3)怎么計算網絡傳播耗時?
網絡傳播耗時=request耗時+response耗時,即數據包在網絡上傳輸的耗時。
具體是:
- 1)數據發送速率(kbps) = TCP窗口大小 * 8 / RTT(TCP);
- 2)數據發送耗時 = 數據量 * 8 / 發送速率;
- 3)網絡傳播耗時 = request + response = RTT + 數據發送耗時。
以某接口為例:TCP RTT=50ms,窗口大小=16192,上行字節數=4095,下行字節數=24807,總網絡耗時(50分位)=300ms。
那么:數據發送速率(bps)=16192*8/0.05=2590720(bps)、數據發送耗時=(4095+24807)*8/3598222*1000=89ms、網絡傳播耗時=50+89=139ms。
最終得出:實際弱網耗時(比中位數多一倍網絡耗時)=300+139=439ms;實際正常耗時(小于中位數)=300ms。
網絡發生擁塞時,我們以16192作為TCP窗口大小;以上數據全部以50分位計算。
9.4線上數據
某接口的弱網準確率:

數據說明:
- 1)判斷弱網:指弱網診斷輸出當前網絡類型為弱網,即HttpRTT類型為2G、3G或吞吐量類型為2G;
- 2)判斷弱網-慢請求率:在判斷為弱網的前提下,實際上慢請求的比例。慢請求指上文中的實際弱網,即高于50分位耗時+網絡傳播耗時;
- 3)判斷弱網-快請求率:在判斷為弱網的前提下,實際上快請求的比例。快請求指上文中的實際正常,即低于50分位耗時;
- 4)弱網率:判斷弱網的請求量占總請求量的比例。
可以看到:判定為弱網的用戶占比約為0.65%,而判定弱網中僅有約5%的請求耗時是低于50分位耗時。
某請求HttpRTT類型、吞吐量類型與耗時表現:

當我們以具體的HttpRTT類型、吞吐率類型來區分數據的時候,更能夠清晰大看到不同分類下的數據差異。通過圖15我們可以看到:判斷的類型越差(4G>3G>2G),請求耗時的越高(AVG、P50...),同時慢請求率越高(準確率),快請求率越低(誤判率)。
9.5不準確性來源
即使我們盡力優化策略,但仍有一部分誤判。
其來源如下:
- 1)分位值判定帶來的誤差 (可以參考以上準確性說明);
- 2)用戶RTT 恰好在閾值附近波動;
- 3)來自算法本身限制,中位數機制本身就決定了當用戶網絡環境快速變更后無法快速響應。比如,用戶從WIFI信號差的地方移動到路由器附近,網絡質量立即變得極好,此時沒有足夠數量的新樣本積累,弱網診斷輸出的仍是弱網;
- 4)大量請求異常高耗時,當用戶吞吐量一般時, 用戶高并發請求某HOST時,存在首包耗時異常高的情況。如圖16第9秒、26秒、26秒都存在單HOST高并發高耗時,而導致估算值較高。(當然,后面我們會考慮優化并發數,但這和弱網診斷沒有關系)。
圖20 - 用戶C全部請求及HttpRTT、吞吐率:
10、應用場景
1)并行Http2.0連接:我們知道HTTP/2.0 支持多路復用,可以同一個TCP連接上進行并發的數據交換,而不像HTTP/1.1需要多個TCP連接。在正常網絡環境下能夠降低一部分延遲,然在弱網環境下TCP的隊頭阻塞問題將使請求變得更慢,那么我們在弱網時,并行建連多個H2連接將能在一定程度上緩解此問題。
2)全站加速:應用全站加速時,客戶端會建連邊緣節點,而不是中心業務服務器,這將能降低客戶端到服務器的TCP RTT,從而響應速度、成功率得到提升,弱網環境下更契合全站加速的應用場景。
3)音視頻降質:弱網時,降低畫質以快速加載要好于加載速度慢。
4)預加載優化:當前App內有各式各樣的預加載,其本意是為了加速頁面訪問速度,提升用戶體驗。然而弱網時可能會適得其反。弱網時,根據網絡環境評級,逐步降低預加載數據,將帶寬讓給前臺網絡請求將能提升頁面加載速度。
11、本文小結
通過監聽App內的Http請求,按照一定策略采集HttpRTT、吞吐率樣本,以加權中位數(時間權重、信號強度權重)的方式綜合估算出HttpRTT、吞吐率,最后根據HttpRTT、吞吐率按照一定的策略劃分出真實網絡類型。
經線上數據驗證,弱網用戶占比0.65%,而弱網請求中僅5%以下耗時低于50分位,具有明顯的區分性。后續可應用于網絡連接優化、全站加速、音視頻降質等多個場景。
12、參考資料
[1] 通俗易懂-深入理解TCP協議(下):RTT、滑動窗口、擁塞處理
[2] 現代移動端網絡短連接的優化手段總結:請求速度、弱網適應、安全保障
[3] 移動端IM開發者必讀(一):通俗易懂,理解移動網絡的“弱”和“慢”
[4] 全面了解移動端DNS域名劫持等雜癥:原理、根源、HttpDNS解決方案等
[5] 美圖App的移動端DNS優化實踐:HTTPS請求耗時減小近半
[6] 百度APP移動端網絡深度優化實踐分享(三):移動端弱網優化篇
[7] 愛奇藝移動端網絡優化實踐分享:網絡請求成功率優化篇
[8] 美團點評的移動端網絡優化實踐:大幅提升連接成功率、速度等
[9] IM開發者的零基礎通信技術入門(十二):上網卡頓?網絡掉線?一文即懂!
[10] 淘寶移動端統一網絡庫的架構演進和弱網優化技術實踐
13、得物技術團隊的其它文章匯總
得物從0到1自研客服IM系統的技術實踐之路
得物自研客服IM中收發聊天消息背后的技術邏輯和思考實現
得物從零構建億級消息推送系統的送達穩定性監控體系技術實踐
得物基于Electron開發客服IM桌面端的技術實踐
(本文已同步發布于:http://www.52im.net/thread-4684-1-1.html)