背景
為了基于網絡狀況做更細致的業務策略,需要一套網速檢測方案,盡量低成本的評估當前網絡狀況,所以我們希望檢測數據來自于過往的網絡請求,而不是專門耗費資源去網絡請求來準確評估。
指標計算
一般 RTT 作為網速的主要評估指標,拿到批量的歷史請求 RTT 值后,要如何去計算得到較為準確的目標 RTT 值呢?
影響 RTT 值的變量主要是:
- 網絡狀況會隨時間變化;
- 請求來自不同的服務器,性能有差異,容易受到長尾數據影響;
首先參考 Chrome 的 nqe 源碼:https://chromium.googlesource.com/chromium/src/+/master/net/nqe/
權重設計
查閱相關源碼后,發現歷史請求的 RTT 值會關聯一個權重,用于最終的計算,找到計算 RTT 權重的核心邏輯:
void ObservationBuffer::ComputeWeightedObservations(
const base::TimeTicks& begin_timestamp,
int32_t current_signal_strength,
std::vector<WeightedObservation>* weighted_observations,
double* total_weight) const {
…
base::TimeDelta time_since_sample_taken = now - observation.timestamp();
double time_weight =
pow(weight_multiplier_per_second_, time_since_sample_taken.InSeconds());
double signal_strength_weight = 1.0;
if (current_signal_strength >= 0 && observation.signal_strength() >= 0) {
int32_t signal_strength_weight_diff =
std::abs(current_signal_strength - observation.signal_strength());
signal_strength_weight =
pow(weight_multiplier_per_signal_level_, signal_strength_weight_diff);
}
double weight = time_weight * signal_strength_weight;
…
可以看到權重主要來自兩個方面:
- 信號權重:與當前信號強度差異越大的 RTT 值參考價值越低;
- 時間權重:距離當前時間越久的 RTT 值參考價值越低;
這個處理能減小網絡狀況隨時間變化帶來的影響。
半衰期設計
在計算兩個權重的時候都是用pow(衰減因子, diff)
計算的,那這個“衰減因子”如何得到的呢,以時間衰減因子為例:
double GetWeightMultiplierPerSecond(
const std::map<std::string, std::string>& params) {
// Default value of the half life (in seconds) for computing time weighted
// percentiles. Every half life, the weight of all observations reduces by
// half. Lowering the half life would reduce the weight of older values
// faster.
int half_life_seconds = 60;
int32_t variations_value = 0;
auto it = params.find("HalfLifeSeconds");
if (it != params.end() && base::StringToInt(it->second, &variations_value) &&
variations_value >= 1) {
half_life_seconds = variations_value;
}
DCHECK_GT(half_life_seconds, 0);
return pow(0.5, 1.0 / half_life_seconds);
}
其實就是設計一個半衰期,計算得到“每秒衰減因子”,比如這里就是一個 RTT 值和當前時間差異 60 秒則權重衰減為開始的一半。延伸思考一下,可以得到兩個結論:
- 同等歷史 RTT 值量級下,半衰期越小,可信度越高,因為越接近當前時間的網絡狀況;
- 同等半衰期下,歷史 RTT 值量級越大,可信度越高,因為會抹平更多的服務器性能差異;
所以更進一步的話,半衰期可以根據歷史 RTT 值的量級來進行調節,找到它們之間的平衡點。
加權算法設計
拿到權值后如何計算呢,我們最容易想到的是加權平均值算法,但它同樣會受長尾數據的影響。
比如當某個 RTT 值比正常值大幾十倍且權重稍高時,加權平均值也會很大,更優的做法是獲取加權中值,這也是 nqe 的做法,偽代碼為:
//按 RTT 值從小到大排序
samples.sort()
//目標權重是總權重的一半
desiredWeight = 0.5 * totalWeight
//找到目標權重對應的 RTT 值
cumulativeWeight = 0
for sample in samples
cumulativeWeight += sample.weight
If (cumulativeWeight >= desiredWeight)
return sample.RTT
進一步優化
通過歷史網絡請求樣本數據計算加權中值,根據計算后的 RTT 值區間確定網速狀態供業務使用,比如 Bad / Good,這種策略能覆蓋大部分情況,但有兩個特殊情況需要優化。
無網絡訪問場景
當用戶一段時間沒有訪問網絡缺乏樣本數據時,引入主動探測策略,發起請求實時計算 RTT 值。
網絡狀況快速劣化場景
若在某一個時刻網絡突然變得很差,大量請求堆積在隊列中,由于我們 RTT 值依賴于網絡請求落地,這時計算的目標 RTT 值具有滯后性。
為了解決這個問題,可以記錄一個“未落地請求”的隊列,每次計算 RTT 值之前,前置判斷一下“超過某個閾值”的未落地請求“超過某個比例”,視為弱網狀態,達到快速感知網絡劣化的效果。