这本手册怎么用
排查端上问题的难点,不是不知道有哪些原因,而是不知道先看哪里。同一个“页面打不开”,可能是资源 404、接口超时、权限重定向、容器拦截,也可能是旧版本不支持新语法。没有顺序,就会在十几个可能原因之间来回跳。
这份手册按用户能描述的症状组织,而不是按技术模块组织。用户不会说“我的 chunk 加载失败”,只会说“白屏”。每个症状给出四块内容:按概率排序的原因、从外到内的验证步骤、先止血再根治的处置建议、以及这次排查后应该补上的观测。概率排序是经验判断,不是统计结论,实际项目要结合自己的版本与设备分布调整。
手册里的耗时、比例和阈值都是案例假设值,用来演示判断过程。排查时以现场证据为准,不要用一个假设值去否定另一个假设值。
症状总表
| 症状 | 首先怀疑 | 关键证据 | 首查位置 |
|---|---|---|---|
| 白屏 | 产物未加载、根组件异常 | 资源 404、控制台报错 | 发布产物与错误边界 |
| 首屏慢 | 关键资源串行、接口瀑布 | 瀑布图、LCP 元素 | 网络面板与渲染路径 |
| 滚动卡顿 | 长任务、全量渲染 | 长任务、帧耗时 | 主线程与列表实现 |
| 内存增长 | 监听未清理、缓存无上限 | 堆快照、增长趋势 | 页面生命周期与缓存 |
| 请求失败 | 网络切换、鉴权、超时 | 错误码、耗时分布 | 数据层与网关 |
| 状态丢失 | 进程回收、路由卸载 | 生命周期日志、存储快照 | 状态归属与持久化 |
| 崩溃 | 空值、异步异常、OOM | 堆栈、设备分布 | 崩溃平台与符号表 |
| 深链打不开 | 路由未注册、登录拦截 | 落地页、参数丢失 | 路由表与入口配置 |
| 版本兼容异常 | 字段变更、schema 漂移 | 旧版本错误率 | 契约与版本矩阵 |
白屏
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 静态资源未加载或 chunk 404 | 发布时 index 与资源版本错配、CDN 缓存 | 首文档 200,但 JS/CSS 404 |
| 2 | 根组件之前抛出异常 | 初始化代码、Polyfill、第三方 SDK | 控制台有未捕获错误,挂载点为空 |
| 3 | 首屏接口长时间阻塞 | 页面用接口结果决定是否渲染 | 请求 pending 或超时,骨架屏不消失 |
| 4 | 挂载点缺失或被样式隐藏 | 模板改动、容器高度为 0 | DOM 存在但不可见或选择器不匹配 |
| 5 | 登录态失效导致重定向循环 | 鉴权拦截与页面初始化互相等待 | URL 反复跳转,网络面板循环 |
验证步骤
- 先在出问题的版本与设备上复现,记录 URL、版本号、内核版本和网络类型。
- 打开网络面板,确认首文档状态码,再逐个检查 JS、CSS、字体是否 404 或被拦截。
- 查看控制台第一条错误,而不是最后一条。后续错误往往是第一条错误的连锁反应。
- 禁用缓存、换隐私窗口重试。若恢复正常,优先怀疑 CDN 与本地缓存版本错配。
- 关闭 JavaScript 或查看服务端返回的 HTML,判断是渲染路径问题还是客户端执行问题。
处置建议
- 给入口 HTML 设置不缓存或短缓存,静态资源使用内容指纹,避免新旧资源互相引用。
- 增加全局错误边界与路由级错误边界,确保异常时至少渲染可操作的兜底页。
- chunk 加载失败时自动重载一次,并带上版本参数;仍失败则展示“刷新或升级”的明确指引。
需要补的观测
- 白屏率:无 FCP、无根节点内容或错误边界触发的会话占比。
- 资源加载失败率,按版本、CDN 节点、资源名分桶。
- 未捕获异常率与首条错误信息,关联发布版本。
- 首屏接口的 P95 与超时率,区分接口名。
首屏慢
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 关键资源体积过大或串行加载 | 首屏依赖整包、入口未拆分 | 瀑布图显示长串依赖 |
| 2 | 首屏接口瀑布 | 先取用户再取配置再取内容 | 多个接口首尾相接 |
| 3 | TTFB 偏慢 | CDN 未命中、回源慢、服务端排队 | 首字节时间占了大头 |
| 4 | 渲染阻塞的脚本与样式 | 同步脚本、未内联关键 CSS | 主线程长时间无法绘制 |
| 5 | 图片未压缩或尺寸不当 | 原图直出、缺少响应式图片 | LCP 元素是超大图片 |
验证步骤
- 确认“慢”的口径:是 LCP、FCP、可交互时间,还是用户主观等待。不同口径对应不同原因。
- 看瀑布图,标出首屏真正需要的资源,区分关键路径与非关键路径。
- 找到 LCP 元素,确认它是文本、图片还是视频封面,再判断优化方向。
- 检查首屏接口数量与依赖关系,确认是否存在可以并行或合并的请求。
- 分别看 TTFB、资源下载、脚本执行、渲染四段耗时,找出占比最大的一段。
处置建议
- 按路由拆分代码,首屏只加载必要模块,非关键模块延迟加载。
- 首屏接口并行化或聚合,避免“取 A 才能取 B”的串行结构。
- 对 LCP 图片做尺寸适配、压缩与预加载,避免用原图撑首屏。
- 关键 CSS 内联,非关键脚本延期,减少渲染阻塞。
需要补的观测
- LCP P75,按路由、设备档位、网络类型分层。
- TTFB 与资源下载耗时的分段统计。
- 首屏接口数量、P95 与瀑布深度。
- 缓存命中率与回源率。
滚动卡顿
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 长任务占用主线程 | 大计算、复杂 diff、同步解析 | 长任务与卡顿时间重合 |
| 2 | 列表全量渲染 | 一次渲染成百上千项 | DOM 节点数随数据量线性增长 |
| 3 | 布局抖动或强制同步布局 | 读写交替、滚动中测量 | 帧内出现大量 layout |
| 4 | 图片解码与大图 | 滚动时解码原图 | 卡顿集中在图片进入视口时 |
| 5 | 频繁状态更新导致整树重渲染 | 全局状态驱动大范围更新 | 渲染次数远高于交互次数 |
验证步骤
- 录一段性能时间线,确认卡顿是持续性的还是集中在某些操作。
- 看长任务列表,记录每个长任务的耗时与调用栈。
- 统计列表 DOM 节点数与数据量,判断是否需要虚拟化。
- 检查滚动回调里是否做测量、排序、过滤等重计算。
- 临时移除图片或动画,确认卡顿是否消失,缩小范围。
处置建议
- 列表虚拟化或分页,只渲染视口附近的内容。
- 把重计算移出主线程,或拆成可中断的小任务。
- 滚动回调只做轻量记录,复杂逻辑防抖或延后。
- 动画优先使用合成友好的属性,避免触发布局与重绘。
需要补的观测
- 长任务数量与总阻塞时间,按路由分桶。
- 帧耗时分布与慢帧率,按设备档位分层。
- INP 高分位,关联具体交互类型。
- 列表渲染数量与 DOM 节点数。
内存增长
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 监听、定时器、订阅未清理 | 页面卸载后仍持有引用 | 重复进出页面后内存阶梯上升 |
| 2 | 缓存无上限 | 图片、请求结果、路由缓存只增不减 | 缓存条目数持续增长 |
| 3 | 闭包持有大对象或 DOM | 回调捕获了不再需要的引用 | 堆快照中 retainer 指向旧页面 |
| 4 | 全局单例累积 | 事件总线、日志队列、埋点缓冲 | 全局对象体积随会话增长 |
| 5 | 大图与 base64 常驻 | 图片未释放、内联资源过多 | 内存峰值与图片数量相关 |
验证步骤
- 明确“增长”是稳定占用高,还是随操作持续上升。前者可能是预算问题,后者更可能是泄漏。
- 重复进入、退出同一页面若干次,观察内存是否回到基线。
- 采集堆快照,对比两次快照之间的新增对象与 retained size。
- 检查页面卸载路径:监听、定时器、订阅、请求、动画是否都有对应的清理。
- 查看缓存实现,确认是否有容量上限、过期策略与淘汰机制。
处置建议
- 把清理写进页面生命周期,提供统一的注册与释放接口,而不是依赖开发者自觉。
- 缓存设置容量上限与淘汰策略,避免无界增长。
- 大图使用后主动释放,避免长期持有 data URL。
- 全局单例只保留必要状态,日志与埋点缓冲设置上限。
需要补的观测
- 前台与后台内存峰值,按页面与设备档位分层。
- OOM 与被系统杀死的比率,与普通崩溃分开统计。
- 页面停留时长与内存增长的关联。
- 缓存条目数与命中率,避免“用内存换命中”失控。
请求失败与超时
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 网络切换、DNS 或 TLS 失败 | 移动网络不稳定、代理干扰 | 失败集中在特定网络类型 |
| 2 | 超时预算不合理 | 预算短于真实尾延迟,或长到用户放弃 | 超时率与 P95 接近预算 |
| 3 | 重试无退避或去重 | 失败后立即重试,形成风暴 | 重试次数与失败率同时升高 |
| 4 | 鉴权过期与刷新死循环 | 多请求同时触发刷新 | 401 反复出现,刷新接口也被拒 |
| 5 | 服务端 5xx 或限流 | 容量不足、发布异常 | 错误码集中在 5xx 或 429 |
验证步骤
- 先看错误码分布,区分网络错误、超时、4xx、5xx 与业务错误。
- 看耗时分布,确认失败是“快速失败”还是“等到超时”。
- 按网络类型、版本、地域分层,确认是否集中在特定人群。
- 检查重试次数与请求去重,确认失败是否被重试放大。
- 对齐服务端日志与端上请求 ID,确认请求是否真正到达服务端。
处置建议
- 把错误分成可重试、不可重试、结果未知三类,分别处理。
- 重试使用指数退避加随机抖动,并设置总预算与最大次数。
- 相同请求去重,共享进行中的 Promise 或等价机制。
- 鉴权刷新单飞,避免多个请求同时刷新令牌。
需要补的观测
- 请求成功率,明确分母是请求、会话还是用户。
- 错误码分布与超时率,按接口与版本分桶。
- 重试率、去重命中率与离线队列积压。
- 端上耗时与服务端耗时的对照,找出差异段。
状态丢失
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 进程被系统回收 | 后台停留、内存压力 | 冷启动后状态归零 |
| 2 | 路由切换卸载组件 | 状态放在页面组件内 | 返回后表单为空 |
| 3 | 刷新或深链重建 | 状态只存在内存 | 刷新后回到默认值 |
| 4 | 存储写入失败 | 配额、权限、序列化异常 | 本地存储无记录或旧值 |
| 5 | 多标签或多实例竞争 | 同一键被覆盖 | 最后写入者获胜,内容错乱 |
验证步骤
- 明确“丢失”的是哪一类状态:草稿、滚动位置、筛选条件、登录态还是未发送消息。
- 复现路径要包含切后台、杀进程、刷新、返回、深链进入等不同入口。
- 查看生命周期日志,确认页面卸载与恢复时分别执行了什么。
- 检查本地存储快照,确认写入是否成功、键名是否稳定、值是否可反序列化。
- 在多标签或多实例下复现,确认是否存在并发覆盖。
处置建议
- 把状态分级:可丢、可恢复、不可丢。不可丢的状态必须持久化并校验完整性。
- 草稿自动保存,节流写入,恢复时提示“已恢复上次编辑”。
- 路由切换时把需要保留的状态提升到路由层或持久层,而不是留在页面组件。
- 存储写入失败要有降级与提示,不能静默吞掉。
需要补的观测
- 状态恢复成功率,按入口(冷启动、深链、返回)分层。
- 草稿丢失率与存储失败率。
- 冷启动恢复耗时,避免恢复本身拖慢首屏。
- 多标签冲突次数与覆盖告警。
崩溃
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 空值与类型假设错误 | 接口字段缺失、可选值未处理 | 堆栈指向属性访问 |
| 2 | 异步异常未捕获 | Promise、回调、定时器 | 堆栈无明确业务入口 |
| 3 | 内存 OOM | 大图、缓存、泄漏累积 | 崩溃前内存接近上限 |
| 4 | 原生或 WebView 兼容问题 | 旧系统、旧内核、厂商定制 | 集中在特定机型与系统版本 |
| 5 | 第三方 SDK 异常 | 广告、统计、推送 | 堆栈落在 SDK 包名 |
| 6 | 并发与时序问题 | 多线程、竞态、重复初始化 | 偶发,难以稳定复现 |
验证步骤
- 先确认崩溃类型:脚本异常、原生崩溃、OOM、ANR 还是被系统杀死。
- 看聚合后的堆栈,找最内层的业务代码帧,而不是最外层的框架帧。
- 按版本、机型、系统版本、页面分层,确认是否集中在特定组合。
- 检查符号表是否完整,未符号化的堆栈只能看到地址,无法归因。
- 用相同数据与操作路径尝试复现,记录前置状态与网络条件。
处置建议
- 对外部数据做边界校验,接口字段缺失时使用默认值并记录告警。
- 全局捕获未处理异常,异步入口统一包装,避免异常穿透到进程层。
- 错误边界按路由与关键模块划分,单点失败不要拖垮整个应用。
- 第三方 SDK 隔离初始化,失败时降级而不是阻断主流程。
需要补的观测
- 崩溃率,明确按会话还是按用户统计,并保留启动次数分母。
- Top 崩溃堆栈与影响用户数,按版本与机型分桶。
- OOM 率与 ANR 率,单独统计,不与普通崩溃混合。
- 崩溃前的关键路径:页面、操作、网络、内存。
深链打不开
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 路由未注册或路径不匹配 | 大小写、尾斜杠、参数格式 | 命中兜底页或首页 |
| 2 | 登录拦截丢失目标 | 跳登录时未保留原始地址 | 登录后回到首页 |
| 3 | 参数编码错误 | 中文、特殊字符、嵌套 URL | 参数被截断或乱码 |
| 4 | 版本不支持该路由 | 旧客户端没有新页面 | 仅旧版本失败 |
| 5 | 入口配置缺失 | Universal Link、App Link、Scheme | 浏览器打开而不是应用 |
| 6 | 冷启动与热启动处理不一致 | 两套入口逻辑 | 热启动正常,冷启动失败 |
验证步骤
- 分别从浏览器、扫码、推送、站外链接进入,确认是否所有入口都失败。
- 分别测试冷启动与热启动,确认两条初始化路径是否一致。
- 分别在登录与未登录状态测试,确认登录回跳是否保留目标。
- 检查原始链接与最终落地页,逐段对比参数是否丢失或变形。
- 在旧版本上测试,确认是否为新路由能力缺失。
处置建议
- 建立路由白名单与显式兜底页,未匹配时给出可操作提示,而不是静默回首页。
- 登录拦截保存完整目标地址,登录成功后原样回跳。
- 统一参数编解码与规范化规则,对中文和嵌套 URL 做专门测试。
- 新旧版本通过能力探测或版本协商决定落地页,不支持时引导升级。
需要补的观测
- 深链到达率:从点击到目标页面成功渲染的比例。
- 失败原因分布:路由未匹配、登录拦截、参数错误、版本不支持。
- 各入口的到达率对比:浏览器、扫码、推送、站外。
- 登录回跳成功率。
版本兼容异常
可能原因(按概率排序)
| 排序 | 原因 | 为什么常见 | 支持的证据 |
|---|---|---|---|
| 1 | 服务端删除或改语义字段 | 新接口未保留旧字段 | 旧版本解析失败或显示空 |
| 2 | 枚举与可空性理解不一致 | 新增枚举值、字段变为可空 | 旧版本落入未知分支 |
| 3 | 本地缓存 schema 漂移 | 旧数据被新代码读取 | 升级后首次启动异常 |
| 4 | 资源与入口版本错配 | CDN 缓存、灰度混合 | 新旧资源交叉引用 |
| 5 | 缺少强制升级兜底 | 旧版本无法完成核心流程 | 旧版本错误率持续升高 |
验证步骤
- 明确兼容窗口:当前需要支持多少个旧版本、多长时间。
- 用旧版本客户端回放核心流程,确认登录、首屏、下单等路径是否可用。
- 对比接口契约变更,检查字段删除、类型变化、枚举新增与可空性。
- 清空本地缓存与保留旧缓存各测一次,确认 schema 迁移是否正常。
- 检查灰度与 CDN 配置,确认入口与资源版本是否一致。
处置建议
- 接口遵循向后兼容:新增字段可选、旧字段保留、枚举新增有默认分支。
- 客户端对未知字段与未知枚举做容错,不因新值崩溃或白屏。
- 本地数据带 schema 版本,升级时执行迁移,失败时安全重建。
- 入口 HTML 与静态资源版本绑定,避免灰度期间交叉引用。
需要补的观测
- 版本分布与旧版本留存曲线。
- 旧版本错误率、白屏率与核心流程成功率。
- API 版本命中分布与契约告警。
- 缓存迁移失败率与升级后首次启动异常率。
跨症状的排查顺序
单个症状内部有顺序,多个症状并存时也需要顺序。建议始终从外向内:
- 先确认发布与产物:版本号、入口 HTML、静态资源、CDN 与灰度状态。产物错了,后面所有分析都建立在错误前提上。
- 再确认入口与路由:用户是从哪里进来的,落到哪个页面,参数是否完整。入口错了,页面本身的代码可能完全没问题。
- 然后确认网络与数据:请求是否发出、是否到达、返回什么、耗时多少。数据错了,渲染层只能呈现错误。
- 接着确认渲染与状态:组件是否挂载、状态是否恢复、错误边界是否触发。
- 最后确认设备与系统:内核、内存、权限、后台限制。前四层都正常时,问题往往在环境差异。
这个顺序不能保证一次命中,但能避免“在业务代码里找了半天,最后发现是 CDN 缓存了旧 index”。
记录模板
每次排查至少留下一份可复盘的记录。结构不必复杂,但要能让别人按同样步骤复现:
症状:
首次发现时间 / 版本 / 平台:
影响面(用户数、会话数、版本、设备、地域):
复现步骤:
关键证据(截图、日志、堆栈、请求 ID):
根因:
临时处置:
长期修复:
需要补的观测:
负责人 / 截止时间:
记录里最重要的是“关键证据”和“需要补的观测”。前者让结论可验证,后者让同类问题下次更早被发现。只写“已修复”的记录,对团队没有积累价值。
常见错误与边界
- 跳过发布与产物检查,直接读业务代码。大量白屏与兼容问题根因在产物层。
- 只看最后一条错误。第一条错误往往才是根因,后面的是连锁反应。
- 用单台设备复现失败就下结论。端上问题高度依赖设备、网络与版本组合。
- 把超时当成失败。超时的请求可能已经成功,写路径要按“结果未知”处理。
- 只修表象不补观测。同样的问题会在下一个版本以另一种症状回来。
- 把概率排序当统计结论。它只是排查起点,现场证据优先。
- 忽略旧版本。兼容窗口内的旧客户端仍然是真实用户。
- 排查记录只写结论。没有证据与复现步骤,结论无法被复核。
小结
端上排查的核心不是记住所有原因,而是建立稳定的顺序:先产物与入口,再网络与数据,然后渲染与状态,最后设备与系统。每个症状都按这个顺序走一遍,能排除大部分误判。
比修复更重要的是补观测。每一次排查暴露的盲区,都应该变成下一次的指标、日志或告警。附录 A 定义了指标口径,这份手册把口径用到具体症状上;下一份附录则把两者收进评审清单,让问题在发布前被问到。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。