线上 H5 页面加载慢到离谱------静态资源 6 秒、API 接口 7 秒。服务器在国内,CDN 也配了,后端接口只要 2ms......慢在哪?
排查下来踩了三个坑:CDN 源站配了已下线的 IP → 动态接口也走了 CDN → 新域名 DNS 又解析到了旧 IP。
三个坑的根因都一样------之前做过一次服务器缩容,机器下线了,但 CDN 源站、DNS 解析这些关联配置没同步清理,埋下了三颗"定时炸弹",直到这次部署新应用才全部引爆。
一、问题现象
部署应用后,访问 H5 页面加载非常慢。F12 看 Network:
- 静态资源(JS/CSS):单个文件加载 6 秒+

- API 接口(JSON):多个接口响应超过 5 秒

环境信息:
- 应用服务器:阿里云 ECS(国内)
- CDN:阿里云 CDN(已配置)
- 用户位置:国内
- 前端现状 :静态资源和 API 接口共用同一个
www域名,全部走 CDN
二、排查过程
1. 排除后端问题(2ms,无辜躺枪)
用 curl 测一个典型 API 接口的各阶段耗时(curl 更多用法可参考 curl 实战:程序员必备的接口调试与性能排查利器):
shell
curl -o /dev/null -s -w "\nDNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\n总耗时: %{time_total}s\n" \
'https://www.example.com/api/v1/config' \
-H 'Content-Type: application/json' \
--data-raw '{"id":"123","code":"test"}'
DNS: 0.051s
TCP: 1.064s ← TCP 连接就花了 1 秒
TLS: 1.089s
TTFB: 7.321s ← 首字节 7 秒
总耗时: 7.322s
名词解释(看懂这几个指标,排查网络问题事半功倍):
指标 全称 含义 慢了说明什么 DNS DNS Lookup 域名解析耗时 DNS 服务器慢或解析链路长 TCP TCP Connect TCP 三次握手耗时 服务器远或网络链路差 TLS TLS Handshake SSL/TLS 握手耗时 证书链太长或未启用 TLS 1.3 TTFB Time To First Byte 从请求发出到收到第一个字节的时间 最关键指标------包含了 DNS + TCP + TLS + 服务端处理 + 网络传输 总耗时 Total Time 整个请求从发出到完成 TTFB 小但总耗时大 = 响应体太大或带宽不足
初步看 TTFB 7 秒,像是后端慢。但查了 SkyWalking 链路日志和 Nginx 日志:
log
[request_time=0.002][upstream_time=0.002]
后端 1ms,Nginx 2ms,完全没问题。7 秒不在服务端。
2. 排除网络链路问题(9ms,也没问题)
shell
ping www.example.com # 9ms
sudo mtr -r -c 20 www.example.com # 全程最高 20ms
dig www.example.com # 解析到 kunlunaq.com(阿里云 CDN)
| 指标 | 结果 | 判断 |
|---|---|---|
| ping 延迟 | 9ms | 国内正常 |
| mtr 全程 | 最高 20ms | 正常 |
| dig 解析 | 阿里云 CDN 节点 | 走了 CDN |
网络链路也没问题。
3. 关键对比:走 CDN vs 直连源站(真相大白)
用 --resolve 绕过 CDN,直连源站 IP 做对比测试:
shell
# 走 CDN(域名自然解析到 CDN 节点)
curl -o /dev/null -s -w "TCP: %{time_connect}s\nTTFB: %{time_starttransfer}s\n" \
'https://www.example.com/api/v1/config' ...
# 直连源站(绕过 CDN)
curl -o /dev/null -s -w "TCP: %{time_connect}s\nTTFB: %{time_starttransfer}s\n" \
--resolve www.example.com:443:源站IP \
'https://www.example.com/api/v1/config' ...
API 接口对比:
| 路径 | TCP | TTFB | 结论 |
|---|---|---|---|
| 走 CDN | 12ms | 7 秒 | CDN 回源巨慢 |
| 直连源站 | 48ms | 165ms | 源站没问题 |
| Nginx upstream_time | - | 2ms | 后端没问题 |
静态资源对比:
| 路径 | TTFB | 总耗时 | 下载速度 |
|---|---|---|---|
| 走 CDN | 6.2s | 6.35s | 72 KB/s |
| 直连源站 | 0.16s | 0.36s | 1.2 MB/s |
CDN 比直连慢了 40 倍。问题 100% 在 CDN 层。
进一步检查 CDN 缓存头:
Content-Length: 459201 ← 没有 gzip 压缩,460KB 原文传输
Age: 0 ← 每次都回源验证
X-Cache: HIT TCP_REFRESH_HIT ← 缓存命中但回源验证(304),验证这一趟花了 6 秒
三、坑 1:CDN 源站配了已下线的 IP
查看阿里云 CDN 控制台的源站配置:

源站配了 6 个 IP,其中 3 个是之前缩容已下线的服务器。 CDN 回源时轮询到这些不可用 IP,连接超时后才切到正常节点,导致回源耗时 6~7 秒。
修复:删除旧 IP
在 CDN 控制台删除 3 个已下线的 IP,只保留在线的服务器。

静态资源从 6 秒降到 700ms,改善明显。但 API 仍比直连慢 8 倍:
| 指标 | 修复前 | 删旧 IP 后 |
|---|---|---|
| API TTFB | 7 秒 | 1.27 秒(直连只要 165ms) |
| 静态资源 TTFB | 6.2 秒 | 722ms |
→ API 还是慢,因为 POST 请求也在走 CDN 绕路。
四、坑 2:动静未分离,API 请求也走了 CDN
前端的静态资源和 API 接口共用同一个 www 域名:
nslookup www.example.com
→ www.example.com.w.kunlunaq.com (CDN 节点)
所有 POST API 请求也先到 CDN,CDN 再回源到 ECS------多绕了一圈。CDN 本身是为静态资源加速设计的,对动态 POST 请求不但没有加速效果,反而因为回源增加了延迟。
修复:动静分离
| 域名 | 用途 | 是否走 CDN |
|---|---|---|
www.example.com |
H5 页面 + 静态资源(JS/CSS/图片) | ✅ 走 CDN |
api.example.com |
后端 API 接口 | ❌ 直连源站 |
前端修改 API 的 base URL 指向 api.example.com,DNS 直接解析到源站 ECS IP,不经过 CDN。
同时优化了两个次要问题:
- Nginx 配置静态资源长缓存 :
Cache-Control: public, max-age=2592000, immutable,解决 CDN 每次回源验证的问题 - 开启 gzip 压缩:460KB JS 压缩后约 120KB
切完域名,发布------然后,又挂了。
五、坑 3:新域名的 DNS 也解析到了旧 IP
切完 api.example.com 后,页面上所有接口直接 pending。
用 curl 一测,TCP 连接直接卡了 75 秒:
DNS: 0.008s ← DNS 正常
TCP: 75.054s ← TCP 连接 75 秒!连不上
TTFB: 75.154s
总耗时: 75.154s
DNS 只要 8ms,但 TCP 75 秒------域名解析到的 IP 不对。有了之前的经验,第一时间去查 DNS:

果然,api.example.com 的 DNS 记录还指向了 3 台已下线的服务器 IP------同一个坑,第三次踩。
修复:关闭旧 DNS 记录
关闭 3 条旧解析记录后恢复正常:
DNS: 0.002s
TCP: 0.045s ← 45ms,正常了
TTFB: 0.144s ← 144ms,完美
总耗时: 0.144s

六、效果对比
| 指标 | 最初 | 修坑 1 后 | 修坑 2 + 3 后 |
|---|---|---|---|
| API 接口 TTFB | 7 秒 | 1.27 秒 | 144ms |
| 静态资源 TTFB | 6.2 秒 | 722ms | ~200ms |
| 页面请求数 | N 个 | N 个 | N 个(后续 CORS 优化后减半,见第八节) |
| 页面整体加载 | 10 秒+ | 3~4 秒 | 1~2 秒 |
七、经验总结
三个坑,根因都是一样的:服务器缩容后,关联配置没清理干净。
| 坑 | 在哪漏了 | 后果 |
|---|---|---|
| 坑 1 | CDN 源站 IP | 回源超时 6~7 秒 |
| 坑 2 | 架构层面:动静未分离 | POST 请求白白绕 CDN |
| 坑 3 | DNS 解析记录 | TCP 连接 75 秒 |
缩容后必须检查的配置清单:
| 检查项 | 说明 |
|---|---|
| CDN 源站 IP | 删除已下线的 IP |
| DNS 解析记录 | 关闭/删除指向旧 IP 的记录 |
| 负载均衡后端 | 移除已下线的服务器 |
| 监控 target | 清理不存在的采集目标 |
| 防火墙/安全组 | 清理旧 IP 的白名单规则 |
排查方法论:
页面慢的时候,别急着怀疑后端------先用
curl -w分段计时,再用--resolve绕过 CDN 对比。TCP 连接 75 秒?那一定不是后端的锅。curl 的更多实战用法,可以参考:curl 实战:程序员必备的接口调试与性能排查利器
八、额外优化:干掉 CORS 预检请求
动静分离后,前端从 www.example.com 请求 api.example.com 的接口,触发了跨域。浏览器对每个 API 请求都先发一次 OPTIONS 预检请求(Preflight),相当于每个接口调用变成了两次请求:

预检请求本身也要走一次完整的 TCP + TLS + 后端处理,耗时和真实请求差不多------等于页面加载时间直接翻倍。
修复:让浏览器缓存 CORS 策略
后端响应头加上 Access-Control-Max-Age,告诉浏览器"这个跨域策略 24 小时内有效,不用每次都问我":
java
// Spring Boot CORS 配置
conf.setMaxAge(Duration.ofDays(1)); // 缓存 24 小时
对应的响应头:
http
Access-Control-Max-Age: 86400
效果立竿见影------刷新页面,预检请求全部消失:

动静分离带来的"副作用":分了域名就有跨域,有跨域就有预检。记得加
Access-Control-Max-Age,否则优化了半天的 TTFB,全被预检请求吃回去了。
附:常见问题
mtr 报错 Failure to start mtr-packet: Invalid argument
需要 root 权限:
shell
sudo mtr -r -c 20 www.example.com