背景
为了基于网络状况做更细致的业务策略,需要一套网速检测方案,尽量低成本的评估当前网络状况,所以我们希望检测数据来自于过往的网络请求,而不是专门耗费资源去网络请求来准确评估。
指标计算
一般 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 值之前,前置判断一下“超过某个阈值”的未落地请求“超过某个比例”,视为弱网状态,达到快速感知网络劣化的效果。