6
头图

前言


对于前端来说,最重要是的体验,而在前端体验中,最为核心的就是性能。秒开率、流畅程度等一系列指标都直接影响用户体验。

因此,建立一个准确、及时、有效的前端性能监控系统,不仅可以量化当前页面的性能水平,还可以为优化方案的效果提供数据支持,此外,还可以在页面性能下滑时提供报警服务,提醒开发人员改善页面性能。

监控指标的选取

在参考前人的实践成果后,我们对性能监控的一系列指标的计算成本,适用性和实用价值进行了评估,认为以下指标和信息是最具实用性和性价比的:

首先是 fcp(first contentful paint,如下图所示),这个指标是目前统计页面秒考虑的主流指标,虽然它不如 fmp(First Meaningful Paint、lcp(Largest Contentful Paint)、speedIndex 等指标更贴近用户真实使用体验,但是优点是在 Android 端通过调用 Performance API 即可获得,在 iOS 可以通过 raf(requestAnimationFrame)估算,实施过程简单。

其次是 tts(time to server),这个指标并没有出现在此前看到的文章里边。

它描述的是用户连接到服务器的时间,通过 Performance API 中提供的 requestStart 减去 fetchStart 得到,这个指标不是前端可以通过技术可以优化的,但是它可以反映在当前用户群体的网络环境下,页面秒开率的上限是多少。

举个例子,假如通过性能监控数据发现,有 15%的用户访问,需要花费至少 1s 的时间才能连接到我们的服务器(可以是 SSR 服务器,也可以是 CDN),那么这些用户无论如何都不能秒开,那么此时,某页面秒开率的上限就是 85%。

假如当前情况下,这个页面的秒开率已经达到了 75%甚至更高,那么继续优化的边际收益会非常低,应该适可而止了。

第三是 tsp(time for server processing),这个指标也没有出现在此前的参考文章里。

它针对的是在使用 SSR 的场景下,服务器内部处理页面请求的耗时,可通过 Performance API 中提供的 responseStart 减去 requestStart 得到。

这个环节性能太差,亦会成为拖累秒开率的一个瓶颈,所以必须予以监控;tsp 太长会压榨其他环节的性能预算,太小会增大服务器运维成本。

第四是 css 文件、图片等资源的大小、xhr 请求的持续时间,前两种资源如果不加节制,会导致页面即便做到秒开,也无法快速进入可用状态,比如常见的 feed 流页面。

对于 xhr,需要进行分类讨论,如果是 SSR 页面,则影响不大,只要保障 tsp 处于较低水平,基本上不会拖累秒开率,但如果是 SPA,页面主要功能区都依赖后端数据支持的,比如判断权限、展示 feed 流内容,xhr 的响应速度就非常关键了,也需要予以监控,在指标下滑时,通知后端予以优化和处理。

最后是一些环境信息,比如用户实用什么品牌和型号的手机,是在微信、浏览器、还是我们的哪个版本的得物 App 内打开 H5 页面。

当页面的性能出现问题时,这些辅助信息能帮助我们尽可能精准地复现用户的实用场景,高效、准确地解决问题。

系统架构

整个系统由以下模块构成:

SDK:负责采集用户的页面性能数据和基本信息,按照一定的发送策略将性能数据发往 SLS,植入页面后可以自行采集性能数据,不需要和页面代码交互。

SLS:阿里云日志服务,接受 SDK 发送的数据数据,并为性能数据添加接收时间,ip 等附加信息。

Backend:性能数据后端服务,这个模块有两个功能,一个是定时从 SLS 拉取原始的性能数据,对其进行去重和加工,得到性能指标数据和用户信息数据,然后将这些数据分门别类地存入对应的数据表中,以备查询。另一个是为数据可视化提供接口数据。

DB:持久化处理后的性能日志数据和性能指标数据的数据库。

Report: 性能数据报表,通过和操作报表,观察特定项目、版本下的指定页面的各项性能指标。

各模块关系如下图所示:

关键技术决策

在进行开发之前,需要对系统的几个关键点进行思考和决策。大致有以下几个点:

  1. 在移动端,unload 事件并不是总能触发,所以需要 SDK 能够间歇性地发送数据到 SLS。为了控制发送的频率,同时减少数据的重复量,我们采取了一种发送间隔逐步延长的策略,即当前页面打开的时间越来越长,数据发送的频率会越来越低,该页面打开的时间达到一定长度后,SDK 将彻底停止工作。
  2. 由于数据存在重复,就需要对用户的端(浏览器、微信、app 内的 webview)进行指纹计算,这里我们选用了 fingerPrintJS2,在计算时,我们去掉了导致指纹不稳定的浏览器特征,使用户在打开页面的时候,总是有固定的指纹。但是只依赖指纹是不够的,因为同一个型号的手机,算出来的指纹很可能是一样的。所以,在对性能数据去重的时候,就要同时结合用户的指纹、日志的客户端时间戳、用户设备的 ip 来进行去重,使用同一个 wifi,同一种设备,在同一毫秒打开页面的用户,以目前前端页面的访问量和时间分布来判断。这个去重方案的效果还是非常好的。
  3. SDK 有一部分同步代码,并且需要在页面载入后尽可能及早运行。这意味着如果 SDK 报错,会导致页面无法正常运行,这是非常危险的。所以用 try catch 包裹 SDK 的同步代码,确保 SDK 的异常不会拖累页面。
  4. 某些页面加载的图片、发送的请求会非常多,全部记录加以上报是非常不现实的,所以我们在监控这部分内容时,只把文件字节数排名前 10 的图片,加载时长排名前 10 的请求的详情记录下来,然后统计一下这个页面加载过多少图片,发送过多少请求。这样既知道页面资源加载的规模,又能找到页面加载最耗时的资源。
  5. 原始日志选择发送到 SLS,主要是因为这个数据发送的并发量非常大,自己做日志服务器成本过高,用 SLS 是一个比较划算的选择。
  6. 后端服务选择用 Python+Django,Python 属于脚本语言,虽然性能差了些,但是对于前端同学来说,上手难度会低一些,对于小规模的后端服务,开发效率也能有保障。同时采用 pypy 编译器,可以改善 python 代码的运行速度。此外,通过使用多进程+多线程,可以进一步提高数据的处理速度。
  7. 由于我们的目的进行统计分析,所以并没有必要统计所有的性能数据,为此,我们采取了等步长采样的方法,即针对一天的数据,我们每 5%的日志数据,只取前 1000 条进行统计,由于从用户侧上报数据这一行为是随机的,所以这个方案基本上可以保障采样的随机性。这样一来,统计工作的计算量就大幅下降了,保证我们可以用一个性能很弱的机器就可以完成数据处理工作,也可以保证一旦出现故障,我们有时间“追回”之前没有处理的数据。
  8. 数据库方面我们选择了 MySQL,我们对 IO 并没有非常苛刻的需求,所以中规中矩的关系型数据库即可满足需求。

未来发展规划

目前的前端性能监控系统能满足日常的监控需求,但是还可以更进一步:

  1. 对帧率的统计:SDK 目前其实有统计帧率的功能,但是由于原始帧率的数据量过大,所以后期需要转换帧率的统计方式,例如只上报卡顿的时间区间和该区间的帧率分布。
  2. 使用 lcp、fmp 等更科学的指标来替代 fcp。
  3. 额外结合 pageName 去进行去重,进一步提高去重的效果。

文|老狼

关注得物技术,携手走向技术的云端


得物技术
846 声望1.5k 粉丝