在日常运维工作中,前端同学常向运维反馈:“静态资源访问很慢”“页面加载时间长”“CDN 好像没生效”之类的问题。作为运维,不能简单地把锅甩给前端或 CDN 厂商,而是要有一套系统的排查思路。
静态资源变慢的原因可能打在网络、服务器、应用甚至客户端等多个环节:DNS 解析延迟、CDN 节点调度异常、源站带宽或磁盘 I/O 瓶颈、Web 服务器配置不当、缓存策略不合理……快速锁定问题源头,正是运维的核心能力。
本文从网络链路、服务器、应用、客户端多个维度,梳理一套可操作的排查方法。

1 排查前的信息收集
1.1 明确问题现象
不同现象往往指向不同排查方向。先问清楚这几件事:
- 是所有用户都慢,还是只有部分地区?前者多半是源站问题,后者可能与 CDN 节点或网络路由有关。
- 是偶发还是持续?偶发可能是瞬时负载或网络抖动,持续则要查配置和瓶颈。
- 首次访问慢、刷新后变快?缓存可能没生效。
- 具体哪些资源慢:图片、CSS、JS、字体?
- 用户的浏览器、网络环境如何?
1.2 基础连通性测试
先确认网络基本通不通:
# 测试 DNS 解析
nslookup static.example.com
dig static.example.com
host static.example.com
# 测试到源站的连通性和延迟
ping -c 10 origin.example.com
# 测试到 CDN 节点的连通性和延迟
ping -c 10 cdn.example.com
# 端口可达性
nc -zv origin.example.com 80
nc -zv origin.example.com 443
telnet origin.example.com 80
# TCP 连接建立耗时
time nc -zv origin.example.com 80
1.3 查看 HTTP 响应时间
用 curl 精确度量各阶段耗时:
# 一次请求看全:DNS、TCP、SSL、首字节、总耗时
curl -o /dev/null -s -w \
"DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nSSL: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
https://static.example.com/js/app.js
# 多次取平均
for i in {1..5}; do
curl -o /dev/null -s -w "Time: %{time_total}s\n" https://static.example.com/js/app.js
done
# HEAD 请求只看响应头
curl -I -s https://static.example.com/js/app.js
# 查看缓存相关响应头
curl -I -s https://static.example.com/js/app.js | grep -i "cache\|expires\|last-modified\|etag"
2 网络层面排查
2.1 DNS 解析问题
DNS 是浏览器获取资源 IP 的第一步,解析慢会直接拖累整体加载。
检查解析耗时:
# 详细解析信息
dig +stats static.example.com
# 指定公共 DNS 测试
dig @8.8.8.8 static.example.com
# 批量测试多个域名
for domain in static1.example.com static2.example.com; do
time_dns=$(dig +stats $domain | grep "Query time:" | awk '{print $4}')
echo "$domain: ${time_dns}ms"
done
常见的 DNS 慢原因:本地 DNS 性能差、递归链路长、权威服务器响应慢、缓存未命中、域名被劫持/污染。应对办法:换用更快的公共 DNS(如 8.8.8.8、1.1.1.1),启用本地 DNS 缓存(dnsmasq、nscd),加上 <link rel="dns-prefetch"> 预解析,或者接入专业的 DNS 服务。
2.2 CDN 相关问题
如果用了 CDN,需要确认它是否真正在工作。
判断 CDN 是否生效:
# 查看响应头中 CDN 特有字段
curl -I -s https://static.example.com/js/app.js | grep -i "x-cache\|x-cdn\|cf-\|server\|via"
# 看返回的是 CDN 节点 IP 还是源站 IP
curl -I -s https://static.example.com/js/app.js | grep -i "^server:"
# 对比直接回源和走 CDN 的耗时
time curl -o /dev/null -s https://origin.example.com/js/app.js
time curl -o /dev/null -s https://cdn.example.com/js/app.js
# 查看 CDN 节点标识
curl -I -s https://static.example.com/js/app.js 2>&1 | grep -i "X-Served-By\|X-Cache\|X-Request-Id"
CDN 缓存未命中是慢请求的大头之一。检查命中状态:
# 第一次请求(可能 MISS)
curl -I -s https://static.example.com/js/app.js | grep -i "x-cache\|miss\|hit"
# 第二次请求(应 HIT)
curl -I -s https://static.example.com/js/app.js | grep -i "x-cache\|miss\|hit"
# 查看缓存控制头
curl -I -s https://static.example.com/js/app.js | grep -iE "cache-control|expires|last-modified|etag"
CDN 配置常见坑点:域名未正确解析到 CDN 节点;源站配置错或不可达;缓存 TTL 太短;缓存 key 未包含必要参数导致相同资源却频繁回源;节点与用户地理位置不匹配;HTTPS 证书问题;CDN 流量超限被限流。
2.3 网络路由和延迟
用 traceroute 或 mtr 追踪网络路径:
# Windows
tracert static.example.com
# Linux traceroute
traceroute -m 30 static.example.com
# mtr 结合了 traceroute 和 ping,持续追踪
mtr -r -c 10 static.example.com
mtr -rw -c 10 static.example.com
# 查看各跳延迟和丢包
mtr -rwbc 50 static.example.com
分析时留意:如果某跳延迟突然飙高,说明该节点可能拥塞或异常;部分超时可能是防火墙挡了 ICMP;最终 IP 若不是预期的 CDN 节点 IP,可能路由被劫持。
3 服务器层面排查
3.1 Web 服务器配置检查
Nginx 配置检查:
# 检查语法
nginx -t
# 查看主配置和 vhost
cat /etc/nginx/nginx.conf
cat /etc/nginx/conf.d/*.conf
# 检查工作进程数、连接数、CPU 亲和性
grep -E "worker_processes|worker_connections|worker_cpu_affinity" /etc/nginx/nginx.conf
# 是否开启 gzip
grep -r "gzip" /etc/nginx/
# 是否配置 keepalive
grep -r "keepalive" /etc/nginx/
关键优化点:worker_processes auto(自动匹配CPU核心数)、worker_connections 影响并发、gzip on 大幅减少传输量、expires 设置缓存时长、tcp_nodelay 和 tcp_nopush 优化 TCP、open_file_cache 缓存文件元数据。
Apache 配置检查:
# 语法检查
apachectl configtest
# 查看加载模块
apachectl -M
# 查看 MPM 类型
apachectl -V | grep -i mpm
# 查看虚拟主机
apachectl -S
3.2 带宽和网络 I/O 监控
判断服务器带宽是否成为瓶颈:
# 查看接口流量
cat /proc/net/dev
iftop -i eth0
nethogs
# 端口级带宽占用
iptraf-ng
# sar 查看网络 I/O 统计
sar -n DEV 1 5
# TCP 连接状态分布
netstat -an | awk '/^tcp/ {s[$NF]++} END {for(k in s) print k, s[k]}'
# 当前连接数概要
ss -s
粗略计算一下:一个页面 50 个静态资源,平均每个 50KB,总大小 2.5MB。用户 10Mbps 带宽(≈1.2MB/s),光下载就要 2 秒多。源站或 CDN 带宽不足时,还会产生排队。
3.3 磁盘 I/O 排查
静态资源最终要从磁盘读出,磁盘 I/O 瓶颈会直接拉高响应时间。
# 查看磁盘使用率
df -h
# 磁盘 I/O 使用率
iostat -x 1 5
iotop
# 哪些进程在大量读写
ps aux --sort=-%io | head -10
# 具体 I/O 操作
pidstat -d 1 5
如果磁盘 I/O 很高,可以考虑换 SSD、启用 Nginx open_file_cache、将静态资源放到 tmpfs、用 CDN 分担压力、优化文件存储结构减少随机读。
3.4 系统负载排查
用 top / htop 看负载全景:
# 系统负载、CPU、内存
top
htop
# 按 CPU 排序
ps aux --sort=-%cpu | head -10
# 按内存排序
ps aux --sort=-%mem | head -10
# 等待 I/O 的进程
ps aux --sort=-%io | head -10
# 查看各核负载(在 top 中按 1)
top
# 然后按 1
当 load average 接近或超过 CPU 核心数时,先解决系统本身的性能问题再说。
4 应用层面排查
4.1 Web 服务器请求日志分析
Nginx 日志配置和常用分析:
# 日志格式
grep "log_format" /etc/nginx/nginx.conf
# 常见字段:$request_time(总处理时间)、$upstream_response_time(上游响应时间)
# 找出 request_time 超过 1 秒的慢请求
awk '{if($NF > 1) print}' /var/log/nginx/access.log | tail -20
# 按响应时间降序
awk '{print $NF, $0}' /var/log/nginx/access.log | sort -rn | head -20
# 哪些 URI 最慢
awk '{sum[$7]++} END {for(uri in sum) print sum[uri], uri}' /var/log/nginx/access.log | sort -rn | head -20
# 统计慢请求数量
awk '{if($NF > 0.5) print "slow"}' /var/log/nginx/access.log | wc -l
awk '{if($NF > 1) print "very_slow"}' /var/log/nginx/access.log | wc -l
4.2 缓存配置检查
# Cache-Control
curl -I -s https://static.example.com/js/app.js | grep -i "cache-control"
# Expires
curl -I -s https://static.example.com/js/app.js | grep -i "expires"
# ETag / Last-Modified
curl -I -s https://static.example.com/js/app.js | grep -iE "etag|last-modified"
# 对比不同资源的缓存策略
for file in /js/app.js /css/style.css /images/logo.png; do
echo "=== $file ==="
curl -I -s https://static.example.com$file | grep -iE "cache|etag|last-modified"
done
最佳实践:静态资源(JS、CSS、图片、字体)设置长缓存,例如 Cache-Control: max-age=31536000, public,用文件名哈希(如 app.a1b2c3d4.js)控制版本更新;同时保留 ETag 或 Last-Modified 支持条件请求。频繁变更的资源可用较短缓存(几分钟到几小时),并确保 CDN 侧也同步缓存规则。
4.3 请求大小和压缩检查
# 测试 gzip 是否启用
curl -I -s -H "Accept-Encoding: gzip" https://static.example.com/js/app.js | grep -i "content-encoding"
# 对比压缩前后大小
curl -s -H "Accept-Encoding: gzip" https://static.example.com/js/app.js | wc -c
curl -s https://static.example.com/js/app.js | wc -c
# 测试 Brotli
curl -I -s -H "Accept-Encoding: br" https://static.example.com/js/app.js | grep -i "content-encoding"
4.4 连接复用检查
# Connection 头
curl -I -s https://static.example.com/ | grep -i "connection"
# 复用连接 vs 新连接耗时对比
time curl -s -o /dev/null https://static.example.com/js/app.js
time curl -s -o /dev/null https://static.example.com/js/app.js
# 强制关闭连接
curl -I -s -H "Connection: close" https://static.example.com/js/app.js
5 客户端层面排查
5.1 浏览器开发者工具
Chrome DevTools 的 Network 面板是前端性能分析的利器:
- 勾选 “Disable cache” 模拟首次访问
- 右键列标题可添加 “Response”、“Initiator”、“Waterfall” 等列
- 利用 Filter 过滤特定类型资源
- 点击某个请求可查看详细 Timing 信息
Timing 各阶段含义:
- Queueing:请求在浏览器队列中等待
- Stalled:等待可用连接(含 DNS、TCP、SSL)
- DNS Lookup:DNS 解析时间
- Initial connection:TCP 握手
- SSL:TLS 握手
- Request sent:发送请求耗时
- Waiting (TTFB):等待服务器首字节
- Content Download:下载内容耗时
如果 TTFB 很长,问题在服务端;Content Download 很长,可能是带宽或资源太大;Queueing 长则说明并发受限或服务器处理不过来。
5.2 浏览器缓存检查
Network 面板 Size 列会显示:
(from memory cache) / (from disk cache) 表示命中浏览器缓存
(pending…) 表示还在排队下载
强制刷新用 Ctrl+F5 / Shift+F5 或开发者工具右键 “Clear browser cache”。
5.3 并发连接限制
浏览器对同一域名默认只允许 6 个并发连接,资源多时就会排队。HTTP/2 的多路复用能打破这个限制,一个 TCP 连接可同时发送多个请求。
检查服务器是否启用 HTTP/2:
curl -I -s -p https://static.example.com/ | grep -i "upgrade\|http2"
curl -I -s -p --http2 https://static.example.com/ | grep -i "http"
6 常见问题场景与解决方案
6.1 场景一:首次访问慢,重复访问快
现象:用户第一次打开页面很慢,刷新后明显变快。
根因:浏览器/ CDN 缓存未生效,资源每次都从源站拉取。
排查:DevTools Network 勾选 Disable cache 观察首次和后续差异;检查 Cache-Control、Expires 头;确认 CDN 命中状态。
解决:配置长缓存头(max-age=31536000),使用文件名哈希控制更新;CDN 设置匹配的缓存规则;也可考虑 Service Worker 做更精细控制。
6.2 场景二:CDN 显示 HIT 但仍很慢
现象:响应头标记 HIT,但用户体感延迟高。
根因:CDN 节点离用户太远、节点负载高、CDN 内部网络拥塞。
排查:从多地域测速,分析 traceroute,查看 CDN 监控面板。
解决:选择更靠近用户的节点或使用 Anycast,启用 CDN 的性能优化功能(协议优化、压缩),必要时引入多 CDN。
6.3 场景三:部分区域用户访问慢
现象:只有某些省份或国家慢,其他正常。
根因:网络路由绕路、CDN 未覆盖该区域、运营商劫持或限速。
排查:收集受影响地区信息,用各地测速工具,获取用户侧 traceroute。
解决:如果是 CDN 覆盖问题,更换或增加 CDN 厂商;路由问题联系 ISP 或 CDN;启用 DNS 智能解析按地域调度。
6.4 场景四:大量小文件导致响应慢
现象:每个文件才几 KB,但请求数极多,总耗时居高不下。
根因:每个请求都要经历 TCP 握手(无 keep-alive 时)和 DNS 等开销,加上浏览器并发限制。
排查:看 DevTools 瀑布图,大量请求处于 Stalled 状态。
解决:合并小文件(CSS sprites、JS bundle),升级 HTTP/2,必要时内联小资源(data URI)。
6.5 场景五:图片加载慢
现象:图片明显比其他资源慢。
根因:图片未压缩、尺寸过大、格式老旧、没用 CDN。
排查:检查文件大小、是否使用 WebP 等现代格式、CDN 是否加速。
解决:压缩图片,使用响应式图片(srcset),采用 WebP/AVIF,配置 CDN 图片处理(裁剪、压缩、格式转换)。
7 自动化监控和告警
7.1 静态资源响应时间监控脚本
#!/bin/bash
# monitor_static.sh - 静态资源响应时间监控
STATIC_URLS=(
"https://static.example.com/js/app.js"
"https://static.example.com/css/main.css"
"https://static.example.com/images/logo.png"
)
THRESHOLD=1 # 秒
ALERT_EMAIL="ops@example.com"
LOG_FILE="/var/log/static_monitor.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}
send_alert() {
echo "Static resource $1 took $2s (threshold: ${THRESHOLD}s)" | mail -s "Static Resource Alert" $ALERT_EMAIL
}
for url in "${STATIC_URLS[@]}"; do
response_time=$(curl -o /dev/null -s -w "%{time_total}" "$url")
if (( $(echo "$response_time > $THRESHOLD" | bc -l) )); then
log "SLOW: $url - ${response_time}s"
send_alert "$url" "$response_time"
else
log "OK: $url - ${response_time}s"
fi
done
7.2 CDN 缓存命中率监控
#!/bin/bash
# monitor_cdn_cache.sh - CDN 缓存命中率监控(Cloudflare 示例)
API_KEY="your_cloudflare_api_key"
ZONE_ID="your_zone_id"
response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/analytics/dashboard?since=60&until=0" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json")
cache_rate=$(echo $response | jq '.result.totals.cache.ratio * 100')
echo "CDN Cache Hit Rate: ${cache_rate}%"
if (( $(echo "$cache_rate < 80" | bc -l) )); then
echo "WARNING: CDN cache hit rate is ${cache_rate}% (expected > 80%)" | mail -s "CDN Cache Alert" ops@example.com
fi
8 结论
静态资源访问慢很少是单一原因造成,从 DNS、网络、服务器到客户端,每个环节都可能藏着瓶颈。排查的核心在于:先界定问题范围(全体 or 部分用户?)、把握时间特征(持续 or 偶发?)、定位是哪个阶段慢(DNS、连接、TTFB、下载)。
主力工具组合:curl 测服务端、traceroute/mtr 验路由、浏览器 DevTools 看客户端行为、日志分析定位异常请求、系统工具(top/iostat/iftop)检视服务器负载。
常态化监控和告警是防患于未然的关键。静态资源性能优化没有银弹,需要结合业务、用户分布和技术栈持续演进。
参考资料:
man curl
man nginx
- Chrome DevTools Network Panel 文档
- Mozilla Developer Network - HTTP 缓存
- Webpagetest.org
- CDN 提供商文档(Cloudflare、阿里云 CDN、AWS CloudFront 等)
本文由云栈社区整理,更多运维实战经验与技术交流,欢迎访问云栈社区。