前端向架构突围系列 - 性能观测 [7 - 2]:前端全链路性能监控系统 (APM)

123 阅读5分钟

写在前面

很多团队依赖后端日志来排查前端问题,这是隔靴搔痒。 后端接口 200 OK,不代表前端渲染成功;CDN 资源 404,后端根本感知不到;JS 逻辑报错,后端更是一脸懵逼。

真正的 前端监控 (RUM - Real User Monitoring) 必须运行在浏览器端

市面上有 Sentry、Datadog 等成熟产品,为什么架构师还要懂怎么从 0 到 1 搭建?

  1. 成本: SAAS 真的很贵,流量大了买不起。
  2. 定制: 你需要结合业务数据(如:用户等级、订单 ID)进行深度分析。
  3. 原理: 理解了原理,你才能更好地使用工具,而不是被工具限制。

image.png


一、 系统架构:数据的旅程

一个完整的 APM 系统,其数据流向可以概括为四个阶段:

  1. 采集 (Collection): 埋伏在浏览器里的 SDK,负责监听一切。
  2. 上报 (Reporting): 也就是“如何把数据运出去”,不仅要快,还不能阻塞业务。
  3. 清洗与存储 (Processing): 原始数据是乱七八糟的,需要解析 UserAgent、映射 SourceMap。
  4. 消费 (Visualization): 报警大屏、性能趋势图、错误堆栈还原。

二、 采集层:SDK 的核心设计

SDK 是监控系统的“探头”。作为架构师,设计 SDK 时要遵循 “无侵入、低开销” 的原则。

2.1 性能采集:PerformanceObserver

别再用 performance.timing 了,那个 API 已经过时且精度不够。 现代监控 SDK 必须使用 PerformanceObserver API,它可以被动订阅各种性能事件。

// 核心代码:订阅 LCP 和 CLS
const observer = new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (entry.entryType === 'largest-contentful-paint') {
      console.log('LCP:', entry.startTime);
      // report(entry.startTime)
    }
    if (entry.entryType === 'layout-shift' && !entry.hadRecentInput) {
      console.log('CLS:', entry.value);
      // accumulateCLS(entry.value)
    }
  }
});

observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.observe({ type: 'layout-shift', buffered: true });

架构要点: 加上 buffered: true 很重要,否则你会漏掉 SDK 初始化之前发生的性能指标。

2.2 错误捕获:不仅仅是 try-catch

前端错误分三种,SDK 需要全覆盖:

  1. JS 运行时错误: window.onerror
  2. Promise 未捕获异常: window.onunhandledrejection(这是现代应用最常见的白屏原因)。
  3. 资源加载失败: window.addEventListener('error', handler, true)。注意要在捕获阶段监听,因为资源错误不冒泡。

2.3 行为回溯:还原案发现场

当报错发生时,只有堆栈是不够的。我们需要知道用户在报错前干了什么。 SDK 需要默认劫持(Hook)用户的交互行为,维护一个“行为队列(Breadcrumbs)”:

  • 用户点击了哪个 DOM?
  • 用户路由跳到了哪?
  • 用户发起过哪个 API 请求?

当报错发生时,把这个队列一并上报。这样你就能复现:“哦,原来是先点了A,再点了B,最后接口 500 导致 JS 崩了。”


三、 上报层:优雅的数据传输

监控数据虽然重要,但它是次要任务。绝不能因为上报监控数据,导致业务请求变慢。

3.1 黄金通道:navigator.sendBeacon

这是一个专门为分析数据设计的 API。

  • 特点: 即使页面关闭(Unload),数据也能发出去。
  • 优势: 不占用主线程,不会阻塞页面跳转。
window.addEventListener('unload', () => {
  const data = JSON.stringify(metricsQueue);
  navigator.sendBeacon('/api/report', data);
});

3.2 闲时上报:requestIdleCallback

对于非紧急数据(如性能打点),不要产生 HTTP 请求风暴。 利用 requestIdleCallback,等到浏览器空闲的时候再打包发送。

3.3 抽样策略 (Sampling)

如果你的网站 PV 有 1 个亿,全量上报会让后端数据库原地爆炸。 架构师需要在 SDK 端设计抽样逻辑:

  • 性能数据: 抽样 1% - 10% 即可,足以反映趋势。
  • 错误数据: 必须 100% 上报(或者对同一种错误进行客户端聚合去重)。

四、 处理层:让数据说人话

后端收到的数据是压缩混淆过的代码堆栈,比如 at a.b (app.min.js:1:500)。这对于开发者来说毫无意义。

4.1 SourceMap 还原服务

你需要搭建一个内部服务(通常结合 CI/CD):

  1. 构建时: Webpack/Vite 打包生成 .map 文件。
  2. 上传时:.map 文件上传到监控系统的私有存储(绝不能上传到公网 CDN! )。
  3. 解析时: 监控系统收到报错 line 1, col 500,去私有存储里找对应的 map 文件,通过 mozilla/source-map 库还原出 src/components/Header.vue 的第 25 行。

4.2 智能聚合

同一个 Bug 可能触发 10 万次报错。监控系统必须具备指纹算法 (Fingerprinting) ,将堆栈信息相似的错误聚合为一个 Issue,否则报警群会被消息淹没。


五、 实战建议:自研还是购买?

这是架构师经常面临的决策。

方案 A:购买 SaaS (Sentry / Datadog / 阿里云 ARMS)

  • 优点: 功能强大,部署简单,支持 SourceMap 管理。
  • 缺点: 贵。数据敏感性问题。
  • 适用: 初创团队,快速验证,预算充足。

方案 B:开源自建 (Sentry On-Premise)

  • 优点: 代码免费,功能同 SaaS。
  • 缺点: Sentry 的部署维护极其复杂(依赖 Kafka, Redis, ClickHouse 等一堆组件),需要专门的运维支持。

方案 C:轻量级自研 (SDK + Node + ClickHouse/ES)

  • 优点: 完全贴合业务,成本最低。
  • 缺点: 需要开发资源,初期功能简陋。
  • 适用: 中大型团队,有定制化需求(如需要关联内部的用户行为路径)。

架构师建议: 如果你是中小团队,直接接入 Sentry (SaaS) 是 ROI 最高的选择。不要为了造轮子而造轮子。 如果你是大型互联网公司,通常会基于 Web Vitals 库 + 自研 SDK + 大数据平台 来构建,因为数据量太大,且需要和业务数据打通。


结语:不仅是看,更是感知

搭建好了 APM,我们就像拥有了“千里眼”。 我们可以自信地告诉老板:“昨天下午 3 点,北京地区电信网络的用户,LCP 升高了 20%,原因是 CDN 节点故障。”而不是含糊地说:“好像网络有点抖。”

既然有了眼睛,看到了问题,接下来就要动手解决了。 除了之前学过的代码层面的优化,架构师还需要建立一套分层级的优化战术,从协议层到渲染层,全面围剿性能瓶颈。