周四晚上 11 点 47 分,Slack 消息开始涌入。
“嘿,结账页面好像出问题了。”
网站是不是宕机了?我看到的是一片空白。
“试了三个不同的浏览器,结果都一样。”
我打开了我们的正常运行时间监控器。一切正常,所有检查都通过了。响应时间正常。所有 HTTP 请求都返回 200 状态码。监控器追踪的各项指标都显示网站运行状况良好。
这不健康。
结账页面——整个应用程序中最重要的一页——对所有用户都显示一片空白。这种情况已经持续了大约 40 分钟。我们的监控系统却毫无察觉。
那天晚上我们花了大约四个小时进行调查,凌晨两点才打好补丁,事后总结更是让我不忍卒读。根本原因是一个 JavaScript 运行时错误,是由一个异步加载的第三方 A/B 测试脚本触发的,而此时我们的监控程序已经通过简单的 HTTP 检查返回了 200 状态码并继续执行其他操作。
显示器完全按照设计运行,问题就出在这里。
ping 监控器实际检查什么
让我们准确描述一下大多数正常运行时间监控工具的功能,因为我认为很多开发人员对此概念比较模糊。
基本的正常运行时间监控器会向您的 URL 发送 HTTP 请求。它会检查:
- 服务器有响应吗?
- 状态码是否在 2xx 范围内?
- 响应时间是否低于某个阈值?
就是这样。有些监控程序会添加字符串匹配——比如“检查响应体是否包含单词‘homepage’”之类的。这样稍微好一些,但提升不大。
问题是,你的用户根本不在乎这些。他们只关心页面是否能正常运行——他们期望的内容是否可见,他们需要点击的按钮是否存在,以及他们想要做的事情是否真的可行。
即使 HTTP 200 响应体为空,它仍然是 HTTP 200。即使页面返回的是 shell HTML 但无法挂载任何 React 组件,它仍然是 HTTP 200。即使缓存的 CDN 响应已经过期三周,它仍然是 HTTP 200。
没人会警告你的故障模式
以下是我亲眼所见(或亲身造成)的几种生产环境中发生的、ping 监控器完全无法检测到的事故。
JavaScript 在加载时崩溃
服务器渲染页面并返回有效的 HTML。但客户端代码包中某个地方出现了未处理的异常——例如空引用、导入失败或 API 返回了意外的数据。页面保持空白、部分渲染或卡在加载状态。服务器端运行正常,HTTP 响应也正常,但用户体验却不正常。
这些漏洞尤其令人讨厌,因为它们通常只会影响某些浏览器、某些屏幕尺寸或处于特定状态的用户(已登录与未登录、购物车中有商品与购物车为空)。
CDN 提供过时的内容
您已部署修复程序。您的源服务器正在运行新代码。但是,由于缓存失效未正确传播,或者有人忘记添加缓存清除标头,或者 CDN 的清除 API 返回了 200 但实际上并未执行清除操作,您的 CDN 边缘节点仍然向 80% 的用户提供旧的、有问题的版本。
您的显示器连接到源服务器,一切正常。用户连接到 CDN 边缘服务器,出现故障。
React/Next.js 水合失败
你正在使用服务器端渲染 React 应用。服务器发送 HTML,在你的显示器响应检查中看起来一切正常。然后客户端 JavaScript 尝试“填充”这些 HTML——添加事件监听器、更新状态——结果出了问题。页面看起来渲染成功了,但没有任何交互功能。按钮没有反应,表单也无法提交。填充错误就这么静静地躺在浏览器控制台里,没人会去看。
A/B 测试或功能开关出了问题
有人在您的实验平台中启用了一个新的变体。它对 90% 的用户运行正常。但对于剩下的 10% 用户(例如对照组、特定用户群或具有特定 cookie 的用户),它会注入一段脚本,导致页面布局错乱、关键元素隐藏或出现错误。您的监控人员不属于这 10% 的用户群,因此他看到的是正常的流程。
结账按钮……不见了
CSS 代码更改,模板逻辑错误,某个组件基于略微错误的状态进行条件渲染,导致你的“加入购物车”按钮、“结账”按钮、提交表单——全部消失了。页面乍一看似乎没问题。首页图片还在,导航栏还在,产品图片也都在。但用户真正需要用来促成交易的那个元素呢?不见了。
监控器检查 HTTP 状态码是否为 200 且响应时间是否小于 2 秒是行不通的。
为什么视觉监控在概念上有所不同
这个道理很简单:与其问“服务器是否响应?”,不如问“页面看起来是否正常?”
可视化监控会使用真实的浏览器运行真实的 JavaScript 代码,等待页面渲染完成,然后截取页面屏幕截图,并将其与已知良好的基线进行比较。如果当前屏幕截图与基线之间的差异超过某个阈值,则表明页面发生了变化,系统会发出警报。
这样就能涵盖上述所有情况,因为它评估的是用户实际看到的最终结果,而不是在任何有趣的渲染工作发生之前进行的中间 HTTP 握手。
diff 方法除了用于纯粹的“是否损坏”检测之外,还有其他用途。它还能检测出:
- 你无意中进行的布局更改
- 文本发生了不应该发生的更改(价格错误、文案错误)
- 消失的元素
- 出现的新元素(例如,遮挡内容的 cookie 横幅、您之前不知道的错误消息)
- 部署过程中出现了视觉回归问题,虽然部署看起来一切正常,但却悄悄地破坏了某些东西。
关键在于基线。在一切正常的情况下截取屏幕截图,将其标记为基线,然后后续每次检查都与该基线进行比较。当差异超过设定的阈值时,发出警报。
警报应该包含差异图——而不仅仅是“某些内容已更改”,而是要以可视化的方式清晰地展示具体更改之处,并加以突出显示。这样才能让用户立即采取行动,而不仅仅是发出警报。
实际操作中的陷阱
可视化监控并非万能,它确实存在一些利弊权衡。
动态内容。如果您的页面显示当前时间、实时股票价格或用户自定义问候语,每次检查都会显示这些差异。您需要屏蔽这些区域,或者调整页面结构,将动态内容放置在可预测的位置,以便排除这些差异。
渲染差异。字体在不同机器上的渲染效果略有不同、抗锯齿设置不同、动画元素在过渡过程中被捕捉到——所有这些都可能导致误报,如果你的差异阈值过于敏感的话。你需要一个足够高的阈值来忽略这些干扰因素,但又足够低来捕捉真正的问题。校准这个阈值需要一些调整。
需要身份验证的页面。如果您想监控需要登录才能访问的页面,则需要在截图流程中处理身份验证。这虽然可行(您可以编写登录脚本),但会增加复杂性。
无头 Chrome 的成本。大规模运行 Chrome 实例并非免费。它非常消耗内存,而且与简单的 HTTP 请求相比,截屏需要实时处理。对于许多 URL,每分钟检查一次的成本远高于 ping 监控。
这些都是可以解决的问题,但它们是真实存在的,任何向你推销视觉监控解决方案的人如果不提及这些问题,就是在回避难题。
这让你身处何方?
我并不是说要你扔掉现有的运行时间监控工具。Ping 监控工具价格低廉、速度快,而且对于捕捉最简单的故障非常有用——比如服务器宕机、TLS 过期、DNS 故障等等。保留它们吧。
但要正确看待它们:它们是“网站正常运行”的必要条件,但并非充分条件。“服务器响应 200”和“用户实际可以使用此页面”之间的差距,正是许多生产事故的根源所在,而大多数团队只有在用户反馈后才会发现这些事故。
在周四晚上我描述的那件事之后,我花了一个周末研究各种可视化监控方案,结果却一头扎进了无头Chrome浏览器截图差异分析的原理里。现有的工具都不太合适——要么太贵,要么配置太复杂,要么就是没能真正解决“这个页面视觉上是否有问题”这个问题。
所以我开发了GrabDiff。它会使用无头 Chrome 浏览器按计划截取你的 URL 屏幕截图,并将其与预先存储的基准进行比较,如果发现任何异常,它会将差异图片以附件形式通过电子邮件发送给你。免费套餐支持三台显示器,无需信用卡。
你的正常运行时间监控器现在可能正在欺骗你。希望只是小问题。
X记录空间