文章采集接口(影响网站加载性能的瓶颈中挖掘指标--APM问题)

优采云 发布时间: 2022-03-23 00:25

  文章采集接口(影响网站加载性能的瓶颈中挖掘指标--APM问题)

  APM的全称是Application Performance Monitor,即性能监控

  这个 文章 有三个前提:

  还记得多年前在网上广为流传的一道经典的前端面试题。大体思路是解释从在浏览器地址栏中输入 url 到看到页面的过程。这类问题的迷人之处在于它给了你一张大嘴,它让你信服——原来我们对眼前的很多事情视而不见,背后有这么大的学术问题。随便问。

  我想在这个 文章 中回答的问题也很简单:我怎么知道我的 网站 性能有多慢以及在哪里?这个问题是我在网站上线的时候首先要搞清楚的。

  待解决问题确定指标

  大问题下,有两个子问题需要先搞清楚。

  这两个子问题在我去年的文章《绩效指标的信念危机》中有详细的解释。由于篇幅关系,这里只陈述结论,两个问题的答案密不可分,必须放在一起聊

  总之,onload 或者 DOMContentLoaded 等技术指标是远远不够的,甚至 First Contentful Paint 也离用户的实际感知还差得很远(后面我会证明这一点)。一个好的指标应该尽可能的贴近用户,甚至是与业务深度定制。所以我推荐页面上用来承载核心内容的DOM元素的时序作为性能的核心指标。这个时机很关键,因为它等同于 网站 在此时可用。

  以网站的详情页为例,关键元素为.single-folder-container

  

  但这并不意味着一个指标就足够了,因为如果我们发现指标值不理想,我们就无法确定问题出在哪里。因此,好的数据的作用应该是双向的:即能够准确的反映当前产品的运行状态(从产品到数据),同时通过观察数据,我们也应该能够知道是什么样的产品存在的问题(从数据到产品)。)

  在这个前提下,我们需要从“潜在的”性能瓶颈中挖掘指标。假设影响网站加载性能的因素有:

  如何继续记录两者的加载时间?

  要回答这个问题,我们要不断地问自己,这两类信息是否足以让我们推测问题出在哪里?与单一指标相比,答案是肯定的,但仍有细化的空间。以资源加载为例,参考下图资源时序。资源加载也分为多个阶段:

  

  我们甚至可以诊断出问题出在 DNS 解析还是 TCP 连接阶段。但是,我们不应该详细采集所有指标,有几个因素需要考虑:

  回到确定指标的问题,我们必须面对目前无法知道自己需要什么样的数据的现状,这很正常。确定指标是假设、验证、重新假设和重新验证的收敛过程。尝试总比拖延和让我们更接近正确答案要好。让我们开始采集上面提到的三类指标

  接口问题

  前端工程师必然会陷入的陷阱是只站在前端的角度看问题,而忽略了最重要的接口性能。对于大多数人来说,页面加载可能只是线性的:

  

  但实际上,在 API Request 环节,我们应该从微服务的角度来看问题。从请求到响应的一个请求,将通过不同的微服务来获取数据,如果请求所经过的每一个环节都可以被追踪到的话。这对于定位在线环境中的问题和衡量单个微服务的效率很有用。这是分布式跟踪。目前,这项技术已经相当成熟。Jaegertracing 和 Zipkin 都是分布式跟踪解决方案。

  

  但是,如果你对后端拆分服务层有所了解,如果你想诊断单个微服务的性能在哪里,我们可以继续下钻到单个微服务,比较调用不同服务层方法(服务layer也适用于前端,详细介绍可以参考我去年翻译的这篇文章《Angular Architecture Patterns and Best Practices》)

  我想表达的很明显。要想充分挖掘应用的性能瓶颈,就要同时考察上下游。分割视角得到的结果是有偏差的。

  解决方案采集日志

  如果您有 采集 日志记录的经验,您应该知道日志记录 采集 和输出是两个不同的东西。特别是对于后端程序。日志可以记录在本地文件中,也可以直接在控制台输出,但是在线环境下,需要在专业的日志服务中记录。

  比如NodeJS的开源日志库winston,支持多种transport的集成,transport就是一种存储日志的存储方式。它还支持编写自定义传输,当前选项支持市面上几乎所有主流日志服务。.NET CORE 中的日志记录提供程序是相同的概念

  但是,这种采集日志的“主动”方式并不是最佳实践。构建网络应用的方法论 Twelve-Factor App 曾提出应用本身不考虑日志的存储,只保证日志以 stdout 的形式输出,由环境负责采集和处理日志。这个提议是合理的,因为应用程序不应该也不可能知道它将部署在哪个云环境中,并且不同的环境对日志的处理方式不同

  为了快速失败,我在开发site2share的后端时没有遵循这个概念。当需要登录采集时,直接调用具体平台的采集方法。目前我的日志都记录在 Azure Application Insights 上,所以在记录时我需要调用 Application Insights 客户端方法:AppInsightsClient.trackTrace(message)

  只是在实现层面,可以使用winston代码变得更加优雅。我们可以创建一个logger来达到同时兼容多个日志输出通道的效果。

  const logger = winston.createLogger({

transports: [

new AppInsightsTransport(),

new winston.transports.Console()

]

});

  因为我们测试的是前端性能,而性能数据是在消费者浏览器的网页上生成的。所以我们依赖的是每个用户在访问后通过嵌入页面的脚本主动上传数据

  应用洞察

  我选择 Azure Application Insights 来存储和查询日志的原因之一是 网站我使用从前端(Azure 静态 Web 应用)到后端(Azure 服务应用)甚至 DevOps 的 Azure 服务,自然,官方的 Application Insights 可以更好地与这些服务集成;还有一个更重要的原因是它可以为我们解决分布式追踪的问题。

  您需要在您的应用程序中植入 Application Insights 的 SDK 来采集日志。SDK 支持前端和后端程序。它通过两种方式采集日志,主动采集和被动报告。以JavaScript语言的web应用为例,在页面植入SDK后,会自动采集程序运行时的错误报告,发出的异步请求,console.log(猴子补丁的方式),和性能信息(通过性能 API);您也可以调用 SDK 提供的 trackMetric 和 trackEvent 主动上报自定义指标和事件信息。性能 采集 我们同时使用这两种方法

  我们通常将metrics、log等信息称为遥测(data/item),通常这些数据存储在不同的表中,并与其他数据一起淹没。如何将两者联系起来?Application Insights 用于关联遥测的解决方案很简单:为每条数据提供唯一的上下文标识符 operation_Id。以用户访问页面为例,本次访问生成的数据中的 operation_Id 称为 xyz,那么在 Application Insights 平台上,我们可以通过 xyz 查询关联数据(Kusto 语法)

  我们不仅可以将前端数据与前端数据关联起来,还可以将前端数据与后端数据关联起来,这是我们进行分布式追踪的法宝。对于微服务应用,Application Insights 甚至可以为我们生成一个 Application Map 来可视化服务之间的调用过程和耗时。

  在浏览完本节的技术细节后,我们可以可视化我们需要哪些数据以及它们之间的关系

  资源加载指标

  借助 Performance API,在现代浏览器中采集指标非常容易。无需主动触发,浏览器已经根据每次页面加载的时间线将性能指标信息封装在PerformanceEntry对象中。之后,我们只需要过滤掉需要的数据,比如我们关心的脚本:

  window.performance.getEntries().filter(({initiatorType, entryType}) => initiatorType === 'script' && entryType === 'resource')

  根据上一节的结论,我们就不详细记录资源加载的各个环节的数据了。这里我重点关注采集资源加载的持续时间和资源加载的开始时间,我们从这两个开始可以分别在PerformanceEntry、duration和fetchStart上获取。因为目前在我看来,预加载和缩短加载时间是提高性能的有效手段。如果事后这两个值没有异常,可以考虑多采集指标

  报告元素出现时间

  确认元素出现的那一刻最简单粗暴的方法是通过 setInterval 来轮询该元素是否出现,但是在现代浏览器中我们可以使用 MutationObserver API 来监控元素的所有变化,因此可以以不同的方式提出问题:body标签下.single-folder-container元素什么时候出现,关键代码大致如下

  const observer = new MutationObserver(mutations => {

if (document.querySelector('.single-folder-container')) {

observer.disconnect();

return;

}

});

observer.observe(document.querySelector('body'), {

subtree: true,

childList: true

});

  这里有一个问题:这段代码非常关键且难以测试。

  第一个问题是,例如在 Jest 环境中没有原生的 MutationObserver 对象。如果只是为了通过测试而简单地模拟 MutationObserver 对象测试,那么测试的意义就没有了;

  其次,即使在 Headless Chrome 等支持 MutationObserver 的环境中进行测试,如果你知道它向你报告的元素的时间是正确的?既然你自己不知道确切的时间是什么(即你测试 Lee 的期望),那么 10 秒肯定是不正确的,但是 2.2 秒呢?

  附加性能指标

  理论上,以上两个都是我们期望采集的指标。但是我还想采集两个额外的指标:First Paint 和 First Contentful Paint,简而言之,它们记录了浏览器在绘制页面时的一些关键时刻。这两个指标也可从 Performance API 获得

  window.performance.getEntries().filter(entry => entry.entryType === 'paint')

  Paint Timing 将比纯粹的技术指标更接近用户体验,但我们将拭目以待,看看它与实际用户看到元素出现的情况相比如何。

  后端时间

  我猜有两个可能的性能瓶颈:1)Redis 查询2) MySQL 查询。

  Redis主要用于会话存储,后端由Node.js + ExpressJS搭建,所以不容易监控会话读取性能。所以我优先考虑MySQL的查询性能,比如findByFolderId方法的读取性能统计:

  const findFolderIdStartTime = +new Date();

await FolderService.findByFolderId(parseInt(req.params!.id))

appInsightsClient.trackMetric({

name: "APM:GET_SINGLE_FOLDER:FIND_BY_ID",

value: +new Date - findFolderIdStartTime

});

  总结

  最后,为了在日志平台上找到对应的指标并对指定类型的指标进行统计,我们需要对上述指标进行命名。以下是命名规则。

  上一个即将结束。我已经说明了这个性能采集方案的思路,也基本实现了我们的性能采集脚本。有了这些代码,我们基本可以完成采集单条性能数据

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线