文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

携程弱网识别技术探索

2024-11-28 15:19

关注

一、背景

二、技术方案

2.1 数据采集

2.2 数据处理

2.3 结果输出

三、落地效果

四、未来展望

一、背景

自从2010年携程推出”无线战略“,并发布移动端APP以来,无线研发团队对于客户端网络性能的优化就一直没有停止过。经过这十几年持续不断的优化,目前携程的端到端网络性能已经处于一个相当不错的水平,大盘数据趋于稳定,优化也随之进入 ”深水区“,提升难度巨大。

结合线上的一系列客诉反馈,我们发现即使大盘的数据再优秀,用户网络表现不佳的个例case仍然层出不穷,排查后大部分被我们归因到"弱网”。这部分“弱网”长尾数据相比大盘均值仍有巨大的提升空间,如果可以针对性优化的话,对于提升整体用户体验和减少客诉都有非常明确的价值。

既然要优化“弱网”,那第一步一定是建立相应的“弱网识别模型”,准确识别出弱网场景,本文即探讨携程在弱网识别方面的技术探索,包含技术选型细节和关键的路径思考,欢迎沟通交流。

二、技术方案

图片

携程弱网识别模型的整个工作流程由数据采集、数据处理、结果输出三部分组成,接下来我们顺着流程来逐个剖析相关细节。

2.1 数据采集

2.1.1 可以客观反映网络质量的指标有哪些

说到可以客观反映网络质量的指标,业内定义清晰且获得公认的有如下这些:

2.1.2 携程选择了哪些指标作为模型输入?为什么

对于网络质量识别,业内做的比较早的是Google的NQE(Network Quality Estimator),国内大多数网络质量识别方案也都参考了Google NQE,NQE中识别模型的输入主要是HttpRTT、TransportRTT、DownstreamThroughput这三个指标。

对于HttpRTT、TransportRTT,在应用层和传输层都有很多方式可以采集到,且口径清晰,所以这两个指标被我们纳入采集范围。

对于DownstreamThroughput,我们实践过程中发现,该指标受到用户行为的影响很大,当用户集中操作大量发送网络请求的时候,该指标就偏高,当用户停止操作阅读数据时,该指标就会偏低甚至长时间得不到更新,考虑到指标的波动性,我们不将此指标纳入采集范围。

既然DownstreamThroughput被排除在外,那由他参与计算的BandwidthDelayProduct也被我们排除。

SignalStrength信号强度由于iOS无法准确获取,考虑到多端一致性,也被我们Pass。

NetworkSuccessRate网络成功率这个指标,可能很少被其他方案提及到,我们提出这个指标并将他纳入采集范围的主要原因是,基于RTT的网络识别模型,在遇到网络波动导致的用户大面积请求失败时,无法获取到有效的RTT值,导致识别的准确性和实时性都收到影响,引入网络成功率可以很好的弥补这个缺陷,最终线上生产环境验证也证明了该指标的必要性。

最终,携程的网络质量识别模型采集HttpRTT、TransportRTT、NetworkSuccessRate作为输入指标。

2.1.3 输入的网络指标如何采集

携程的网络请求,主要有Tcp代理通道、Quic代理通道、Http通道三种网络通道。对于上述提到了三个输入指标,我们从如下网络行为中进行数据采集:

对于自定义的Tcp、Quic代理通道,按照上述口径在网络通道相关状态回调内统计数据即可,自定义实现参考价值不大,这里就不过多赘述。

对于标准的Http请求,我们可以通过获取系统网络框架返回的Metric信息或者监听请求的状态流转来获取网络指标。

对于iOS,iOS 10之后NSURLSession支持通过NSURLSessionTaskDelegate的协议方法URLSession:task:didFinishCollectingMetrics:获取到请求的Metric信息,详细信息见附录1,单次请求的Metric定义如下图:

图片

对于Android,系统网络框架OkHttp支持添加EventListener来获取Http请求的状态流转信息,可以在各状态回调内记录时间戳来计算RTT,详细信息见附录2,单次请求的Events定义如下图:

图片

依照上述方法收集到网络数据后,我们把数据封装成对应的结构体,注入识别模型,携程对于网络数据结构体的定义如下,方便大家参考:

typedef enum : int64_t {    


    NQEMetricsSourceTypeInvalid = 0,              // 0
    NQEMetricsSourceTypeTcpConnect = 1 << 0,      // 1
    NQEMetricsSourceTypeQuicConnect = 1 << 1,     // 2
    NQEMetricsSourceTypeHttpRequest = 1 << 2,     // 4
    NQEMetricsSourceTypeQuicRequest = 1 << 3,     // 8
    NQEMetricsSourceTypeHeartBeat = 1 << 4,       // 16
    ......
} NQEMetricsSourceType;


struct NQEMetrics {


    // 本次采集到的数据来源,可以是多个枚举值的或值
    // 例如一次没有连接复用http请求,source = TcpConnect|HttpRequest,同时存在transportRTT和httpRTT     NQEMetricsSourceType source;
    // 本次数据的成功状态,用作成功率计算
    bool isSuccessed;
    // httpRTT,可为空
    double httpRTTInSec;
    // transportRTT,可为空
    double transportRTTInSec;
    // 数据采集时间
    double occurrenceTimeInSec;
};

2.2 数据处理

2.2.1 数据过滤和滑动窗口

网络数据采集后,注入到识别模型内,需要一个数据结构来承载,我们采用的是队列。

进入队列前,我们需要先进行数据过滤,筛选掉一些无效的数据,目前采用的筛选策略有如下这些:

数据过滤后加入队列,为了实时性和结果准确性,我们处理数据时,会根据两个限制逻辑来确定一个具体的滑动窗口,只让窗口内的数据参与计算,具体窗口限制逻辑如下:

每次计算网络质量时,可以根据这两个限制来确定计算窗口,窗口外的数据可以实时清理出队列,减少内存占用。

上文提到的各种阈值设置,均可通过配置系统更新。

2.2.2 动态权重计算

弱网识别模型的原理简单来说就是将窗口内的一组数据经过一系列处理后,得出一个最终值,再用这个最终值与对应的弱网阈值比较来得出是否是弱网。

出于实时性的考虑,我们希望距离当前时间越近的数据权重越高,所以要用到动态权重的算法,这里我们比较推荐的是”半衰期动态权重“和”反正切动态权重“两种算法。

半衰期动态权重

半衰期顾名思义,即每经过一个固定的时间,权重降低为之前的一半。这里衰减幅度和周期都是可以自定义的,计算公式如下:

以衰减幅度为0.5,衰减周期为60秒为例,对应的函数曲线如下:

图片

横坐标为数据采集时间距今的时间差,纵坐标为权重,从图上可以清晰看到,随着时间差增大,权重无限趋近于0。

半衰期动态权重也是Google NQE采用的权重计算方案,Google采用的周期是每60秒降低50%,相关代码详见附录3,部分核心代码如下:

double GetWeightMultiplierPerSecond(
    const std::map& 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);
}


void ObservationBuffer::ComputeWeightedObservations(
    const base::TimeTicks& begin_timestamp,
    int32_t current_signal_strength,
    std::vector* 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());
…
}

反正切动态权重

y=arctan(x)反正切函数在第一象限的取值范围为0~Pi/2,我们将arctan(x)取反,向上平移Pi/2,然后除以Pi/2,函数曲线即可在第一象限随着x增大y的取值从1趋近于0。我们还可以使用一个斜率系数来控制权重降低的趋势快慢,公式推导过程如下:

动态权重 = (Pi / 2  - arctan(abs(now - 数据采集时间) * 斜率系数)) / (Pi / 2) = 1 - arctan(abs(now - 数据采集时间) * 斜率系数)  / Pi * 2;斜率系数为浮点型,取值范围为0~1,系数越小,权重降低的越缓慢。

以斜率系数为1/20为例,对应的函数曲线如下:

图片

和前文的半衰期动态权重相同,横坐标为数据采集时间距今的时间差,纵坐标为权重,随着时间差增大,权重趋近于0,两种动态权重算法效果类似。

反正切动态权重的实现代码如下:

static double _nqe_getWeight(double targetTime) {
    ……
    double interval = now - targetTime;
    /// 曲率系数,数值越小权重降低的越缓慢
    double rate = 20.0 / 1;
    return 1.0 - atan(interval * rate) / M_PI_2;
}

从上图的代码实现可以看出,反正切相关的代码实现要简单很多,但是由于存在推导过程,所以理解起来比较困难,代码维护成本较高(数学功底对于程序员来说也是非常重要的),大家可以酌情自行选择。

携程最终采用的也是半衰期动态权重的方案,出于实时性考虑,最终线上验证后采用的衰减幅度为0.3,衰减幅度为60秒,供参考。

2.2.3 RTT指标加权中值计算

在确定了单条数据的权重之后,对于RTT的数值计算,我们第一个想到的是加权平均,但是加权平均很容易收到高权重脏数据的影响,准确性堪忧,所以我们改用了“加权中值”。

加权中值的计算方式是,将窗口内的数据按照数值大小升序排列,然后从头遍历数据,累加权重大于等于总权重的一半时,停止遍历,当前遍历到的数值即为最终的加权中值。

NQE对于TransportRTT和HttpRTT处理,也是使用的这种方式,相关代码详见附录4,部分核心代码如下:

std::optional ObservationBuffer::GetPercentile(
    base::TimeTicks begin_timestamp,
    int32_t current_signal_strength,
    int percentile,
    size_t* observations_count) const {


……
  // 此处的percentile值为50,即取中值
  double desired_weight = percentile / 100.0 * total_weight;
  double cumulative_weight_seen_so_far = 0.0;
  for (const auto& weighted_observation : weighted_observations) {
    cumulative_weight_seen_so_far += weighted_observation.weight;
    if (cumulative_weight_seen_so_far >= desired_weight)
      return weighted_observation.value;
  }
  // Computation may reach here due to floating point errors. This may happen
  // if |percentile| was 100 (or close to 100), and |desired_weight| was
  // slightly larger than |total_weight| (due to floating point errors).
  // In this case, we return the highest |value| among all observations.
  // This is same as value of the last observation in the sorted vector.
  return weighted_observations.at(weighted_observations.size() - 1).value;
}

2.2.4 成功率指标加权平均计算

对于成功率,我们的NQEMetrics结构体内定义了单次成功状态isSuccessed,单条数据的加权成功率为 (NQEMetrics.isSuccessed ? 1 : 0) * weight,整体的加权成功率为加权成功率总和除以总权重

相关代码实现如下:

extern double _calculateSuccessRateByWeight(const vector &metrics, uint64_t types, const shared_ptr config) {
    ……
    uint64_t totalValidCount = 0;
    double totalWeights = 0.0;
    double totalSuccessRate = 0.0;


    for (const auto& m : metrics) {
        /// 过滤需要的数据
        if ((m.source & types) == 0) {
            continue;
        }
        /// 累计总权重和总成功率
        totalValidCount++;
        totalWeights += m.weight;
        totalSuccessRate += (m.isSuccessed ? 1 : 0) * m.weight;
    }


    /// 数据不足
    if (totalValidCount < config->minValidWindowSize) {
        return NQE_INVALID_RATE_VALUE;
    }
    if (totalWeights <= 0.0) {
        return NQE_INVALID_RATE_VALUE;
    }


    return totalSuccessRate / totalWeights;
}

2.2.5 引入成功率趋势提高实时性

网络质量识别不仅需要准确,实时性也非常重要,在网络质量切换时模型识别的时间越短越好。前文已经提到了TransportRTT、HttpRTT、NetworkSuccessRate三个核心指标的计算,但是在线上实际验证的过程中,我们发现在网络完全不可用成功率跌0后,识别模型对于网络状态的恢复感知很慢,原因是成功率的攀升需要较长的时间。

针对这个极端的case,我们引入了一个“成功率趋势”的新指标,来优化模型的实时性,在成功率未达阈值当时有明显趋势时,提前切换网络质量状态。成功率趋势是指一段时间内成功率连续上升或者下降的幅度,浮点类型,取值范围-1 ~ +1。

成功率趋势初始值为0,计算方式如下:

1)在每次更新成功率时,计算更新前后成功率的差值

如果差值为正,则成功率向好

如果差值为负,则成功率向坏

2)当然还需要过滤一些毛刺数据,避免趋势变化过频

具体代码实现如下:

void NQE::_updateSuccessRateTrend() {
    auto oldRate;
    auto newRate;


    if (oldRate < 0 || newRate < 0) {
        _successRateContinuousDiff = 0;
        return;
    }
    auto diff = newRate - oldRate;
    /// 数据错误,不做处理
    if (abs(diff) > 1) {
        _successRateContinuousDiff = 0;
        return;
    }
    /// diff小于0.01,作为毛刺处理,不影响趋势变化
    if (abs(diff) < 0.01) {
        _successRateContinuousDiff += diff;
        return;
    }
    /// 计算连续diff
    if (diff > 0 && _successRateContinuousDiff > 0) {
        _successRateContinuousDiff += diff;
    } else if (diff < 0 && _successRateContinuousDiff < 0) {
        _successRateContinuousDiff += diff;
    } else {
        _successRateContinuousDiff = diff;
    }
}

在网络成功率和成功率趋势的加持下,我们的识别模型实时性大幅度提升。我们控制相同请求频率和请求数据量,线下模拟弱网切换进行测试,测试结果如下:

所以,最终携程弱网识别模型计算的指标有TransportRTT、HttpRTT、NetworkSuccessRate、SuccessRateTrend(成功率趋势)四个。

2.3 结果输出

2.3.1 网络质量定义

识别模型对外输出的是一个网络质量的枚举值,Google NQE对于网络质量的定义如下,源码详见附录5:

enum EffectiveConnectionType {
  // Effective connection type reported when the network quality is unknown.
  EFFECTIVE_CONNECTION_TYPE_UNKNOWN = 0,


  // Effective connection type reported when the Internet is unreachable
  // because the device does not have a connection (as reported by underlying
  // platform APIs). Note that due to rare but  potential bugs in the platform
  // APIs, it is possible that effective connection type is reported as
  // EFFECTIVE_CONNECTION_TYPE_OFFLINE. Callers must use caution when using
  // acting on this.
  EFFECTIVE_CONNECTION_TYPE_OFFLINE,


  // Effective connection type reported when the network has the quality of a
  // poor 2G connection.
  EFFECTIVE_CONNECTION_TYPE_SLOW_2G,


  // Effective connection type reported when the network has the quality of a
  // faster 2G connection.
  EFFECTIVE_CONNECTION_TYPE_2G,


  // Effective connection type reported when the network has the quality of a 3G
  // connection.
  EFFECTIVE_CONNECTION_TYPE_3G,


  // Effective connection type reported when the network has the quality of a 4G
  // connection.
  EFFECTIVE_CONNECTION_TYPE_4G,


  // Last value of the effective connection type. This value is unused.
  EFFECTIVE_CONNECTION_TYPE_LAST,
};

Google枚举定义的最大问题是理解成本比较高,其他开发同学看到这个所谓的“3G”、“4G”,他依然不知道网络是好是坏,是不是他认为的“弱网”。

所以我们在定义接口的时候,对于枚举的设计考虑最多的就是理解成本,结合开发同学最想知道的“是不是弱网”,我们的接口定义如下:

typedef enum : int64_t {
    /// 未知状态,初始状态或者无有效计算窗口时会进入此状态
    NetworkQualityTypeUnknown = 0,
    /// 离线状态,网络不可用
    NetworkQualityTypeOffline = 1,
    /// 弱网状态
    NetworkQualityTypeBad = 2,
    /// 正常网络状态
    NetworkQualityTypeGood = 3
} NetworkQualityType;

这样是不是看起来简单明了多了,我们接下来就讲讲怎么计算得出这几个枚举的结果。

2.3.2 网络质量计算方式

NetworkQualityTypeUnknown 是在初始化或者网络切换后的一段时间内,数据不足无法得出网络质量,会进入此状态。

NetworkQualityTypeOffline 的触发条件很单一,就是操作系统识别到无网络连接,具体的获取方式由各平台自行实现,例如iOS可以通过Reachability获取,官方Demo详见附录6。

NetworkQualityTypeBad 也就是我们最核心的“弱网”状态,计算方式是上文提到的TransportRTTHttpRTT两个指标任一指标触发弱网阈值,或者NetworkSuccessRate和SuccessRateTrend同时满足弱网阈值。

NetworkQualityTypeGood 是指正常的网络质量状态,上述三种网络质量类型讲完后,这个类型就简单了,即非上述三种情况的场景,归类到Good,这也是设计上占比最高的网络质量类型。

识别模型的运转流程如下:

图片

2.3.3 弱网阈值制定

模型核心的计算逻辑,就是将加工后得到的各网络指标与对应的弱网阈值进行对比,从而获得是否进入弱网的结果,关于弱网阈值的制定上,我们经历了如下两个阶段:

第一阶段,主要参考NQE EFFECTIVE_CONNECTION_TYPE_2G 的阈值定义

第二阶段,我们通过线上的网络质量分布监控,和一些具体case的分析,不断迭代我们的阈值,我们需要制定一个识别准确率的指标来指引阈值的调整工作,达到逻辑准确与自洽。

理论上,我们希望识别模型的入参与当下计算出的网络质量类型所匹配,例如当前注入NQEMetrics数据的HttpRTT <= 1726ms ,那我们预期当前计算出的网络质量类型就是Good。但是弱网的决策逻辑是相对复杂的,需要考虑到各种因素,以下两点会造成弱网状态下的入参数据不一定符合弱网阈值定义:

1)弱网的计算是对过去已经发生网络行为的分析,具有一定的滞后性,所以在识别结果切换附近,必然有部分的原数据已经满足下一阶段的网络质量定义

2)弱网的决策是对多个指标的复合计算,所以在识别到弱网状态时,不一定所有的指标原数据都符合弱网定义,比如由HttpRTT触发弱网时,当前的TransportRTT数据可能表现良好

终上两点原因,弱网分类下必然有一定的非弱网数据,这里的误差数据占比与识别准确率负相关,误差数据占比越低,识别准确率越高。所以想到这里,我们的模型识别准确率的指标计算口径就有了:

有了这个指标指引,我们在模型上线后进行了数个版本的数据统计,通过各指标阈值的微调和case by case解决异常场景,误差数据从刚上线的15%+降低到10%以下,即模型识别准确率优化至90%以上。

最终携程90%准确率的模型对应的弱网阈值如下(不同业务场景的网络请求差别较大,仅供大家参考):

Tips:对于类似携程这种自定义的弱网识别模型,弱网标准也是考虑业务现状的定制标准,所以不需要太多和外部的弱网标准对齐,重点是自洽和符合业务预期

三、落地效果

考虑到识别模型要支持多平台(iOS、Android、Harmony等),所以我们在一开始实现方案时就采用了C++作为开发语言,天然支持了多平台,各平台只需要实现上层数据采集和注入模型的少量逻辑即可完成模型的接入。相同的代码实现和弱网标准,也方便我们在不同的平台间直接对标数据,发现各平台的问题针对性优化。

目前携程的网络质量识别模型,已经在iOS、Android平台完成接入并大面积投产,网络质量数据与集团的APM监控平台打通,形成了携程官方统一的网络质量标准,在网络排障、框架网络优化、业务网络优化等多种场景下扮演重要角色,弱网优化相关的内容我们会在后面相关的专题内继续分享,此处不再赘述。

最终网络质量相关的分布数据如下(数据为实验采集,不代表携程真实业务情况,仅参考):

网络质量分布:

图片

各网络质量下对应的请求性能数据:

图片

四、未来展望

网络质量识别模型的完成只是我们网络优化的开始,后续还有很多的工作需要我们继续努力,未来一段时间我们会从以下几个方面继续推进:

1)持续推进各平台、各独立APP的网络质量识别模型接入,完成携程终端全平台的网络质量模型覆盖

2)做好识别模型的防劣化工作,解决各业务场景的bad case,坚守现阶段识别准确率和实时性的标准水位

3)推出携程内部的“网络性能白皮书”,从APP、系统平台、网络质量、成功率、全链路耗时等各维度解析公司内部各业务线的网络表现,形成内部的网络性能数据基线,为业务优化提供参考

4)借助现有的弱网标准和识别能力,从网络框架侧和业务侧两个不同的角度进行弱网优化,提高整体网络表现;当下海外市场是业务发力的重点,海外场景的网络表现也明显弱于国内,我们会针对海外场景从弱网的角度进行重点优化。

携程目前已经针对弱网场景推出了一系列优化策略,部分策略已经取得非常不错的收益,后续我们会继续推进,也会持续分享输出。

来源:携程技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯