|
| 1 | +# 知识卡片 · 踩坑记录 |
| 2 | + |
| 3 | +## 基本信息 |
| 4 | + |
| 5 | +| 字段 | 内容 | |
| 6 | +|------|------| |
| 7 | +| 标题 | 下载慢诊断中的三次误判:对照实验缺失与观测行为污染测量 | |
| 8 | +| 日期 | 2026-07-31 | |
| 9 | +| 类型 | 踩坑记录 | |
| 10 | +| 标签 | #性能诊断 #CDN #测量方法 #对照实验 #复盘 | |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## 症状 |
| 15 | + |
| 16 | +官网安装包(172MB)下载极慢,浏览器单连接约 140KB/s,用户需等约 20 分钟。目标是定位瓶颈并提速。 |
| 17 | + |
| 18 | +真实根因(最终结论):**CDN 对大文件按分片缓存,未预热时首批请求实时回源,被源站带宽拖垮**。已缓存分片实测可达 12–38MB/s,未缓存分片仅约 110KB/s。 |
| 19 | + |
| 20 | +但抵达该结论前经历了三次误判,每次都被下一步证据推翻。**本卡片的价值不在结论,而在这三次误判的成因。** |
| 21 | + |
| 22 | +## 环境 |
| 23 | + |
| 24 | +| 项目 | 信息 | |
| 25 | +|------|------| |
| 26 | +| 源站 | 阿里云 ECS + Nginx(`/downloads/` 静态托管) | |
| 27 | +| 加速 | 阿里云 CDN(大文件下载加速,回源 ECS) | |
| 28 | +| 客户端 | Windows,RTT 至源站 21ms、至 CDN 节点 5ms | |
| 29 | +| 文件 | 172MB `.exe` 安装包 | |
| 30 | + |
| 31 | +## 三次误判与推翻过程 |
| 32 | + |
| 33 | +### 误判一:「ECS 出网带宽约 4.4Mbps 封顶」 |
| 34 | + |
| 35 | +- **依据**:单连接 143KB/s、3 并发 551KB/s,推断 551KB/s 即带宽上限。 |
| 36 | +- **推翻**:后续 8 并发达 768KB/s,说明 551 并非上限。 |
| 37 | +- **成因**:**用少量样本点外推容量上限**。并发数不足时得到的吞吐不能代表带宽天花板。 |
| 38 | + |
| 39 | +### 误判二:「单流劣化源于跨网丢包,BBR 可解」 |
| 40 | + |
| 41 | +- **依据**:单流仅占多流总吞吐的 1/5,符合丢包导致的 TCP 单流劣化特征;且上游有相似结论。 |
| 42 | +- **推翻**:在生产启用 BBR 后**单连接速率毫无改善**(143→133KB/s);且实测 RTT 仅 21ms,不存在高延迟。 |
| 43 | +- **成因**:**把上游 issue 的相似结论当成本案结论**。改完仍失败时应立即放弃该假设,而非继续在错误方向加码。 |
| 44 | +- **补救**:被证伪的 Defender/BBR 相关注释已按实测改写,避免留下错误认知。 |
| 45 | + |
| 46 | +### 误判三:「瓶颈在客户端本地网络」 |
| 47 | + |
| 48 | +- **依据**:CDN 与 ECS 的单连接速率几乎相同(145 vs 110KB/s)、8 并发也几乎相同(773 vs 768KB/s),两者共同上限暗示瓶颈在客户端。 |
| 49 | +- **推翻**:同一客户端测第三方源(npmmirror CDN)单连接达 **23.8MB/s**,客户端能力高出 160 倍。 |
| 50 | +- **成因**:**缺少对照实验**。只在自己的两个服务间比较,无法区分「两端都慢」与「客户端封顶」。 |
| 51 | + |
| 52 | +### 额外陷阱:对照实验本身一度产生虚假数据 |
| 53 | + |
| 54 | +首次对照测得「8 并发 3316KB/s」,实为**无效数据**:目标 URL 返回 302,而 `curl` 未加 `-L` 不跟随重定向,实际只下载了 97 字节的重定向响应,耗时几乎全是进程启动开销。加 `-L` 后才得到真实的 23.8MB/s。 |
| 55 | + |
| 56 | +### 最后一个陷阱:观测行为污染了被观测对象 |
| 57 | + |
| 58 | +为判断预热是否覆盖全文件,用小 Range 请求(1KB)探测各偏移点的 `X-Cache`,结果**全部显示 HIT**,据此以为预热完成。但随后大范围下载仍只有 224KB/s。 |
| 59 | + |
| 60 | +原因:**小 Range 探测请求本身就会导致该分片被拉取并缓存**,于是探测后必然显示 HIT——HIT 是探测造成的,而非预热造成的。 |
| 61 | + |
| 62 | +正确判据只能是**终端指标**:不同请求量下的实际下载速率。该指标清楚显示 1MB→12.9MB/s、5MB→38.3MB/s、20MB→224KB/s,即缓存只覆盖了前若干 MB。 |
| 63 | + |
| 64 | +## 根因 |
| 65 | + |
| 66 | +阿里云 CDN 对大文件采用分片缓存,仅被请求过的分片进入缓存。未预热时首批用户实时回源,速度等同直连源站——**接了 CDN 却毫无效果**。 |
| 67 | + |
| 68 | +## 解决方案 |
| 69 | + |
| 70 | +1. CDN 侧执行**预热**(`PushObjectCache`),把整个安装包推到边缘节点; |
| 71 | +2. 在 `release.yml` 中**自动化预热**,每次发版同步完安装包后调用预热接口,避免新版本首批用户仍走回源; |
| 72 | +3. 预热对象仅需 `.exe` 与 `.blockmap`;`latest.yml/json` 按设计不缓存且体积极小。 |
| 73 | + |
| 74 | +详见技术方案:[安装包分发加速:CDN 接入方案与下载源可切换设计](../solutions/2026-07-installer-cdn-distribution.md) |
| 75 | + |
| 76 | +## 教训 |
| 77 | + |
| 78 | +- **性能诊断必须有对照实验**:只测自己的服务无法区分「服务端慢」与「客户端/链路慢」。用同一客户端测一个已知高速的第三方源,一次请求即可排除整类因素——这一步若提前做,可直接跳过误判二和三。 |
| 79 | +- **警惕观测行为改变被观测对象**:缓存命中状态、连接数、预取行为等都可能被探测请求本身影响。判断应基于**终端指标**(用户实际感受到的速率),而非中间态指示器。 |
| 80 | +- **对照实验也要验证自身有效性**:留意重定向(`curl -L`)、超时截断(`--max-time` 导致只下载了部分数据)、以及"速率"是否由真实数据量算出。 |
| 81 | +- **上游相似结论只是候选假设**:按其修改后若无改善,应立即放弃并回到证据,同时清理被证伪的改动及其注释,避免给后人留下错误认知。 |
| 82 | +- **少量样本不能外推容量上限**:并发不足时的吞吐不代表带宽天花板。 |
| 83 | +- **分层排除优于猜测**:客户端 → 链路 → CDN 边缘 → 回源 → 源站,逐层用可否证的实验切分,比依据现象"像什么"去猜快得多。 |
| 84 | + |
| 85 | +## 参考 |
| 86 | + |
| 87 | +- 通用误区已补充至 [Debug 标准操作流程 · 常见误区](../../standards/debug-sop.md) |
| 88 | +- 相关实现:`.github/workflows/release.yml`(自动预热)、`server/nginx/nginx.conf`(`/downloads/` 限速与并发) |
0 commit comments