3. 网络瀑布图概念与原理归纳总结
讲网络瀑布图之前,我得先坦白一件事:刚接触 Gradle 构建分析那会儿,我总盯着「BUILD SUCCESSFUL」就完事了,结果项目越来越大,构建越来越慢,我却完全不知道问题在哪。后来在一次客户现场,我对着他们的构建日志发呆,突然想起当年做 Web 优化时用的瀑布图——那玩意儿能一眼看出哪个请求是瓶颈。于是我就想:Gradle 构建本质上也是一堆「请求」啊,依赖下载、任务执行、缓存读取,每个都有开始、等待、完成的时间点。为啥不能也画成瀑布图?
嗯,这就是网络瀑布图在 Gradle 诊断中的价值。说白了,它是把构建过程中所有网络相关的操作(下载依赖、拉取远程缓存、上传 artifact 等)按时间轴展开,让你直观看到哪些操作在排队、哪些在并行、哪个环节耗时最长。
3.1 什么是网络瀑布图
网络瀑布图,最早来自浏览器开发者工具。它展示的是每个 HTTP 请求的生命周期:DNS 查询、TCP 连接、TLS 握手、发送请求、等待响应、下载内容。在 Gradle 里,我们关心的不是浏览器的请求,而是 Gradle 发起的各种网络调用:
- 解析远程仓库的元数据文件(如
maven-metadata.xml) - 下载 POM 或 Gradle Module Metadata
- 下载实际的 JAR/AAR 包
- 上传或下载构建缓存(远程 cache)
- 下载插件或 SDK 组件
每个网络调用都可以用类似瀑布图的条带表示:横轴是时间,纵轴是请求顺序,条带的宽度代表耗时,颜色常用来区分阶段。我个人习惯把条带分成几个关键段:
- 连接阶段:包含 DNS 解析、TCP 连接、TLS 握手。如果这一阶段长,说明网络质量差或 DNS 配置有问题。
- 发送阶段:实际发送请求数据。通常很快,但如果请求体很大(比如上传缓存),就会明显变长。
- 等待阶段(TTFB):从发送完请求到收到第一个字节。这是服务器处理时间,也可能是网络延迟。
- 下载阶段:接收响应体。依赖包大时这一阶段会很长。
3.2 瀑布图的核心原理
你可能会问:瀑布图不就是一堆横条吗?能看出啥来?其实这里面的门道很多。我能总结出三个核心原理:
- 串行与并行:如果瀑布图中的条带是上下堆叠、首尾相连的,说明请求是串行的(一个完成才下一个)。如果条带之间有重叠,说明是并行的。Gradle 默认会并行下载多个依赖,但受限于仓库配置或模块依赖关系,经常会出现不必要的串行。
- 关键路径:从构建开始到结束,最长的那个串行请求链就是关键路径。你优化其他请求都白费,只要缩短关键路径上的请求,整体构建时间才会缩短。我在项目中遇到过:团队优化了所有依赖的下载速度,但构建还是慢,后来发现是一个远程缓存上传请求卡在「等待」阶段,足足占了15秒。
- 性能波动:同一个仓库,不同时间下载同一个文件,耗时可能差好几倍。瀑布图能帮你捕捉这些异常值。我曾见过一个 CI 环境,10次构建里有3次依赖下载特别慢,靠瀑布图对比才定位到是某个镜像节点不稳定。
3.3 瀑布图各阶段与 Gradle 构建的对应
为了让你更清楚地理解,我整理了一张对照表。每个阶段在 Gradle 构建中都有具体的体现:
| 瀑布图阶段 | Gradle 构建中的典型操作 | 常见瓶颈原因 |
|---|---|---|
| DNS 查询 | 解析仓库域名(如 repo.maven.org) | DNS 缓存失效、外网 DNS 慢 |
| TCP 连接 | 与仓库服务器建立 TCP 连接 | 防火墙拦截、代理配置错误 |
| TLS 握手 | HTTPS 安全握手 | 证书验证慢、服务器不支持 HTTP/2 |
| 发送请求 | 上传构建缓存、发布 artifact | 大文件上传带宽不足 |
| 等待响应 (TTFB) | 服务器处理请求,返回开始 | 仓库服务器负载高、依赖解析逻辑复杂 |
| 下载内容 | 下载 JAR/AAR/插件 | 依赖包体积大、带宽限制、慢服务器 |
我曾经在一个项目里,发现每次构建都卡在「下载依赖」阶段,瀑布图上显示某个 JAR 的下载时间长达 40 秒。我一开始以为是文件大,后来换了个仓库镜像,瞬间降到 1 秒。你想想看,如果没瀑布图,我可能还在怀疑是 Gradle 配置问题,完全不会想到仓库服务器本身慢。
3.4 如何获取 Gradle 构建的瀑布图
有两种方式能拿到构建层面的瀑布图数据:
- Gradle Build Scan (建议首选):启用
--scan参数,构建结束后会生成一个网页链接。在「Network Activity」或「Performance」面板里,就能看到每个网络请求的瀑布图。 - 手动抓包 / 代理日志:用 mitmproxy 或 Fiddler 抓取 Gradle 发起的所有 HTTP 请求,再整理成瀑布图。这种方式更底层,适合诊断代理或安全方面的问题。
我自己更喜欢用 Build Scan,因为它把请求和 Gradle 的 Task 执行关联起来了。比如某个 Task 执行慢,点进去能看到它里面触发了哪些依赖下载,还能看到 Task 之间的依赖关系。如果你想让团队一起分析,只需分享 scan 链接就行,连环境都不需要重现。
// 在 Gradle 命令行中添加 --scan 参数
gradle build --scan
// 或者设置持续生成(不推荐长期开启,会影响性能)
// gradle.properties
org.gradle.scan.enabled=true
3.5 一个典型的瀑布图分析案例
我拿一个实际场景举例。曾经有个 Android 项目,本地构建需要 8 分钟,团队一直认为是项目模块太多导致的。我用 --scan 生成报告,瀑布图一出来就发现问题了:
- 前 30 秒:串行下载了 12 个 POM 文件,每个都要先 DNS 解析、TLS 握手,仓库服务器响应很慢(TTFB 平均 2.5 秒)。
- 中间 1 分钟:依赖下载并发了 8 个请求,但其中有一个 30MB 的 AAR 占据大半时间。
- 最后 40 秒:远程缓存上传等待了 15 秒才拿到响应。
我们做了三个优化:换上一个响应更快的私有仓库镜像(TTFB 降到 200ms)、把那个大 AAR 拆成更小的模块、关闭远程缓存的上传(只用只读)。结果构建时间从 8 分钟降到 3 分钟。
这个案例让我坚信:没有数据导向的优化,就是瞎猜。而瀑布图是最直观的数据展现方式,没有之一。
3.6 你一定要记住的几个原则
- 看整体,更看细节:先看瀑布图的总耗时轮廓,再对异常长的条带逐段分析。
- 关注 TTFB 和连接时间:这两个阶段占主导时,问题往往不在 Gradle 本身,而在网络或仓库端。
- 并行不意味着快:如果并行的请求太多,反而可能因带宽争抢导致每个请求都变慢。
- 对比两次构建:优化前拍一张瀑布图,优化后再拍一张,差别一目了然。
好了,关于网络瀑布图的概念和原理,就先聊到这。说白了,它就是一张时间轴上的「请求心电图」,能帮我们精确找到构建中的「血栓」在哪。下一节我会带你手把手解读一张真正的 Gradle 瀑布图,看看那些条带背后的秘密。
gradle build --info 2>&1 | grep -E "Download|Upload|Resolve",虽然没那么直观,但至少能看出哪些操作耗时多。