CDN 出了问题,用户看到的可能是”页面加载慢”“内容没更新”“偶尔报错”。这些模糊的症状背后,需要系统化的排查方法论。本文覆盖 CDN 运维中最常见的四类问题:缓存命中率低、回源异常、内容不一致、性能下降。
一、CDN 响应头解读
排查 CDN 问题的第一步是读懂响应头。CDN 提供商在响应头中暴露了大量调试信息。
1.1 通用 CDN 响应头
# 获取 CDN 响应头(不下载 body)
$ curl -sI https://www.example.com/api/products
HTTP/2 200
date: Sat, 19 Jul 2025 08:30:15 GMT
content-type: application/json
content-length: 4523
cache-control: public, max-age=3600, s-maxage=7200
age: 1847
x-cache: HIT
x-cache-hits: 42
x-served-by: cache-hkg17927-HKG
x-timer: S1721377815.234521,VS0,VE0
via: 1.1 varnish (Fastly)
cf-cache-status: HIT
cf-ray: 8a1234567890-HKG逐头解析:
响应头 │ 含义 │ 调试价值
─────────────────────┼──────────────────────────────────┼──────────────────
Cache-Control │ 源站设置的缓存策略 │ 是否允许缓存
Age │ 对象在缓存中的存活时间(秒) │ 缓存新鲜度
X-Cache │ 缓存状态(HIT/MISS/BYPASS) │ 是否命中缓存
X-Cache-Hits │ 该对象被缓存命中的次数 │ 缓存热度
X-Served-By │ 服务该请求的缓存节点 │ 定位是哪个 PoP
Via │ 中间代理链 │ 请求经过了哪些层
CF-Cache-Status │ Cloudflare 缓存状态 │ Cloudflare 专用
CF-Ray │ Cloudflare 请求追踪 ID │ 关联日志
X-Timer │ 时间戳和耗时 │ 性能分析
1.2 各 CDN 提供商的缓存状态头
Cloudflare (cf-cache-status):
HIT → 从边缘缓存返回
MISS → 缓存未命中,回源获取(已缓存用于后续请求)
EXPIRED → 缓存已过期,回源验证/刷新
STALE → 返回了过期内容(源站不可达时的容错)
BYPASS → 请求被配置跳过缓存(如有 Cookie)
DYNAMIC → 资源被认定为动态内容,不缓存
REVALIDATED → 缓存过期后回源验证,源站返回 304
UPDATING → stale-while-revalidate,返回旧内容同时后台刷新
NONE → 不走缓存
Fastly (x-cache):
HIT → 缓存命中
MISS → 缓存未命中
HIT, HIT → 经过两层缓存都命中(Shield + Edge)
MISS, HIT → Edge 命中,Shield 未命中
MISS, MISS → 完全回源
CloudFront (x-cache):
Hit from cloudfront → 缓存命中
Miss from cloudfront → 缓存未命中
RefreshHit → 缓存过期后回源验证为 304
Error from cloudfront → CDN 层出错
Akamai (x-cache):
TCP_HIT → 缓存命中
TCP_MISS → 缓存未命中
TCP_REFRESH_HIT → 缓存过期后验证仍有效
TCP_MEM_HIT → 从内存缓存命中(比磁盘缓存更快)
1.3 批量分析响应头的脚本
#!/bin/bash
# cdn-header-check.sh — 批量分析 CDN 响应头
URL="${1:-https://www.example.com/}"
ITERATIONS="${2:-10}"
echo "=== CDN Header Analysis: $URL ==="
echo "Iterations: $ITERATIONS"
echo ""
declare -A cache_stats
total_age=0
age_count=0
for i in $(seq 1 $ITERATIONS); do
headers=$(curl -sI "$URL" 2>/dev/null)
# 提取缓存状态
cf_status=$(echo "$headers" | grep -i "cf-cache-status" | awk '{print $2}' | tr -d '\r')
x_cache=$(echo "$headers" | grep -i "x-cache:" | awk '{print $2}' | tr -d '\r')
cache_status="${cf_status:-$x_cache}"
cache_status="${cache_status:-UNKNOWN}"
# 统计缓存状态分布
cache_stats[$cache_status]=$(( ${cache_stats[$cache_status]:-0} + 1 ))
# 提取 Age
age=$(echo "$headers" | grep -i "^age:" | awk '{print $2}' | tr -d '\r')
if [ -n "$age" ]; then
total_age=$((total_age + age))
age_count=$((age_count + 1))
fi
# 提取服务节点
served_by=$(echo "$headers" | grep -i "x-served-by\|cf-ray\|x-amz-cf-pop" | head -1 | tr -d '\r')
printf " #%02d: cache=%s age=%s node=%s\n" "$i" "$cache_status" "${age:-N/A}" "$served_by"
sleep 0.5
done
echo ""
echo "--- Cache Status Distribution ---"
for status in "${!cache_stats[@]}"; do
count=${cache_stats[$status]}
pct=$((count * 100 / ITERATIONS))
printf " %-15s %3d/%d (%d%%)\n" "$status" "$count" "$ITERATIONS" "$pct"
done
if [ $age_count -gt 0 ]; then
avg_age=$((total_age / age_count))
echo ""
echo "--- Age Statistics ---"
echo " Average Age: ${avg_age}s"
fi二、缓存命中率分析
缓存命中率(Cache Hit Ratio, CHR)是 CDN 性能的核心指标。一般来说,静态资源的 CHR 应该在 85% 以上。
2.1 命中率低的常见原因
缓存命中率低的排查清单:
1. Cache-Control 配置问题
├── no-store / no-cache / private → 不缓存
├── max-age 太短(如 60s)→ 频繁过期
├── 源站未设置 Cache-Control → CDN 使用默认策略(可能不缓存)
└── Vary 头太宽泛(Vary: *)→ 每个请求视为不同对象
2. Cache Key 问题
├── Query String 导致缓存碎片
│ /product?id=1&utm_source=google ← 缓存 Key 1
│ /product?id=1&utm_source=twitter ← 缓存 Key 2(内容相同)
├── Cookie 被包含在 Cache Key 中
│ 不同用户的 Session Cookie 导致每个用户单独缓存
└── Accept-Encoding / User-Agent 导致变体过多
3. 长尾内容
├── 大量 URL 各自只被访问 1-2 次
├── 在单个 PoP 的请求量不足以维持缓存
└── 缓存被逐出(LRU)后无法再次命中
4. PoP 分散
├── CDN 节点太多导致每个节点流量不足
├── DNS 负载均衡导致同一用户被路由到不同 PoP
└── 没有启用 Origin Shield(中间层缓存)
5. 动态内容误分类
├── API 响应可以短时间缓存但被标记为 DYNAMIC
├── 需要在 CDN 规则中显式开启缓存
└── 缺少 Cache-Control 头导致 CDN 不缓存
2.2 Query String 优化
Query String 是缓存碎片化的首要原因:
问题场景:
GET /product.jpg?v=1.0&utm_source=google&fbclid=abc123
GET /product.jpg?v=1.0&utm_source=twitter
GET /product.jpg?v=1.0
三个 URL 实际返回相同内容,但 CDN 将它们视为三个独立对象。
Cloudflare 的 Query String 排序和过滤:
解决方案:
1. 忽略无关 Query String(推荐)
CDN 规则: 只用 path 作为 Cache Key,忽略所有 Query String
适用: 静态资源(图片、CSS、JS)
2. Query String 排序
CDN 规则: 对 Query String 参数按字母排序后作为 Cache Key
?b=2&a=1 和 ?a=1&b=2 → 同一个 Cache Key
适用: API 响应
3. 选择性包含
CDN 规则: 只保留特定参数(如 id, page),忽略追踪参数
?id=1&utm_source=google → Cache Key 只包含 ?id=1
适用: 带追踪参数的页面
Cloudflare Workers 实现自定义 Cache Key:
// custom-cache-key.js — 自定义 Cache Key 逻辑
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 定义需要保留的 Query String 参数
const keepParams = new Set(['id', 'page', 'category', 'v']);
// 定义需要忽略的追踪参数
const ignoreParams = new Set([
'utm_source', 'utm_medium', 'utm_campaign', 'utm_content',
'fbclid', 'gclid', 'msclkid', 'ref', '_ga'
]);
// 构建干净的 Cache Key
const cacheUrl = new URL(url.pathname, url.origin);
const sortedParams = [...url.searchParams.entries()]
.filter(([key]) => !ignoreParams.has(key))
.sort(([a], [b]) => a.localeCompare(b));
sortedParams.forEach(([key, value]) => {
cacheUrl.searchParams.set(key, value);
});
// 使用自定义 Cache Key 查找缓存
const cacheKey = new Request(cacheUrl.toString());
const cache = caches.default;
let response = await cache.match(cacheKey);
if (response) {
// 添加调试头
response = new Response(response.body, response);
response.headers.set('X-Cache-Key', cacheUrl.toString());
response.headers.set('X-Cache', 'HIT');
return response;
}
// 缓存未命中,回源
response = await fetch(request);
// 缓存响应
if (response.ok) {
const cloned = response.clone();
event.waitUntil(cache.put(cacheKey, cloned));
}
response = new Response(response.body, response);
response.headers.set('X-Cache-Key', cacheUrl.toString());
response.headers.set('X-Cache', 'MISS');
return response;
}
};2.3 Vary 头的陷阱
Vary 头告诉缓存:对于同一个 URL,不同的请求头值可能得到不同的响应。滥用 Vary 会严重降低命中率:
Vary 头影响分析:
Vary: Accept-Encoding
→ 按 gzip / br / identity 分别缓存
→ 通常 2-3 个变体,影响有限 ✓
Vary: Accept-Language
→ 按语言分别缓存(en, zh, ja, ...)
→ 如果支持 10 种语言 → 10 个变体
→ 命中率下降到 1/10 ⚠
Vary: User-Agent
→ 几乎每个浏览器版本的 UA 都不同
→ 变体数量爆炸 → 命中率接近 0 ✗
Vary: Cookie
→ 每个用户的 Cookie 不同
→ 变体数量 = 用户数 → 完全不可缓存 ✗
Vary: *
→ 等同于不缓存 ✗
最佳实践:
- 静态资源: Vary: Accept-Encoding(仅此一个)
- 需要设备适配: 使用 CDN 的设备检测头替代 User-Agent
- 需要语言适配: 用 URL 路径区分(/en/ /zh/)而非 Vary
2.4 缓存命中率监控
# cdn_metrics.py — CDN 缓存命中率监控
import time
from collections import Counter
from datetime import datetime
class CDNMetrics:
"""CDN 缓存指标采集器"""
def __init__(self):
self.cache_stats = Counter() # {status: count}
self.latency_samples = [] # [(timestamp, latency_ms)]
self.origin_errors = Counter() # {status_code: count}
self.pop_distribution = Counter() # {pop_id: count}
def record_request(self, cache_status, latency_ms, pop_id, origin_status=None):
"""记录一次请求的指标"""
self.cache_stats[cache_status] += 1
self.latency_samples.append((time.time(), latency_ms))
self.pop_distribution[pop_id] += 1
if origin_status and origin_status >= 500:
self.origin_errors[origin_status] += 1
def cache_hit_ratio(self):
"""计算缓存命中率"""
total = sum(self.cache_stats.values())
if total == 0:
return 0.0
hits = self.cache_stats.get('HIT', 0) + \
self.cache_stats.get('STALE', 0) + \
self.cache_stats.get('REVALIDATED', 0) + \
self.cache_stats.get('UPDATING', 0)
return hits / total * 100
def report(self):
"""生成指标报告"""
total = sum(self.cache_stats.values())
chr_value = self.cache_hit_ratio()
print(f"=== CDN Metrics Report ===")
print(f"Time: {datetime.now().isoformat()}")
print(f"Total Requests: {total}")
print(f"Cache Hit Ratio: {chr_value:.1f}%")
print(f"")
print(f"Cache Status Breakdown:")
for status, count in self.cache_stats.most_common():
pct = count / total * 100
print(f" {status:15s} {count:8d} ({pct:.1f}%)")
if self.latency_samples:
latencies = [l for _, l in self.latency_samples]
latencies.sort()
p50 = latencies[len(latencies) // 2]
p99 = latencies[int(len(latencies) * 0.99)]
print(f"")
print(f"Latency:")
print(f" P50: {p50:.0f}ms")
print(f" P99: {p99:.0f}ms")
if self.origin_errors:
print(f"")
print(f"Origin Errors:")
for code, count in self.origin_errors.most_common():
print(f" HTTP {code}: {count}")
if self.pop_distribution:
print(f"")
print(f"Top PoPs:")
for pop, count in self.pop_distribution.most_common(5):
pct = count / total * 100
print(f" {pop:20s} {count:8d} ({pct:.1f}%)")Prometheus + Grafana 的 CDN 监控配置:
# prometheus/rules/cdn-alerts.yml
groups:
- name: cdn_cache
rules:
# 缓存命中率低于 80% 告警
- alert: CDNCacheHitRatioLow
expr: |
sum(rate(cdn_requests_total{cache_status=~"HIT|STALE|REVALIDATED"}[5m]))
/
sum(rate(cdn_requests_total[5m]))
< 0.80
for: 15m
labels:
severity: warning
annotations:
summary: "CDN 缓存命中率低: {{ $value | humanizePercentage }}"
description: "过去 15 分钟 CDN 缓存命中率低于 80%,请检查 Cache-Control 配置"
# 回源 5xx 错误率高
- alert: CDNOrigin5xxHigh
expr: |
sum(rate(cdn_origin_requests_total{status=~"5.."}[5m]))
/
sum(rate(cdn_origin_requests_total[5m]))
> 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "CDN 回源 5xx 错误率高: {{ $value | humanizePercentage }}"
description: "回源 5xx 错误率超过 5%,源站可能异常"
# 缓存 BYPASS 比例异常
- alert: CDNCacheBypassHigh
expr: |
sum(rate(cdn_requests_total{cache_status="BYPASS"}[5m]))
/
sum(rate(cdn_requests_total[5m]))
> 0.30
for: 10m
labels:
severity: warning
annotations:
summary: "CDN BYPASS 比例异常: {{ $value | humanizePercentage }}"三、回源异常诊断
回源(Origin Fetch)失败是 CDN 最严重的故障类型——用户直接看到错误。
3.1 回源超时
回源超时的排查路径:
CDN 边缘 ──[回源请求]──→ 源站
超时类型:
1. 连接超时(Connect Timeout)
→ 无法建立 TCP 连接
→ 原因: 源站 IP 不可达 / 防火墙阻断 / 源站宕机
→ 排查: 从 CDN 边缘 ping/telnet 源站
2. 读取超时(Read Timeout)
→ TCP 连接建立,但源站响应太慢
→ 原因: 源站处理时间长 / 后端服务阻塞 / 数据库慢查询
→ 排查: 检查源站应用日志和慢查询
3. TLS 握手超时
→ TCP 连接建立,TLS 握手卡住
→ 原因: 源站 TLS 配置错误 / 证书问题 / SNI 不匹配
→ 排查: openssl s_client 测试 TLS 握手
模拟和排查回源超时:
# 1. 测试源站可达性(模拟 CDN 边缘视角)
# 使用 CDN 提供的诊断工具或从多个地区测试
# TCP 连接测试
$ nc -zv origin.example.com 443 -w 5
Connection to origin.example.com 443 port [tcp/https] succeeded!
# TLS 握手测试
$ echo | openssl s_client -servername origin.example.com \
-connect origin.example.com:443 -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
# 2. 测量源站响应时间
$ curl -o /dev/null -s -w "
连接: %{time_connect}s
TLS: %{time_appconnect}s
TTFB: %{time_starttransfer}s
总计: %{time_total}s
" https://origin.example.com/api/health
# 如果 TTFB > 10s → 源站处理太慢
# 3. 持续监控源站响应时间
$ while true; do
ttfb=$(curl -o /dev/null -s -w "%{time_starttransfer}" \
https://origin.example.com/api/health)
echo "$(date '+%H:%M:%S') TTFB: ${ttfb}s"
sleep 5
done3.2 回源 5xx 错误
CDN 回源 5xx 错误分类:
502 Bad Gateway
├── CDN 无法连接源站
├── 源站返回了无效的 HTTP 响应
└── 排查: 检查源站是否运行、防火墙规则
503 Service Unavailable
├── 源站主动返回 503(过载/维护)
├── CDN 的 Origin Health Check 失败
└── 排查: 检查源站负载、健康检查配置
504 Gateway Timeout
├── CDN 等待源站响应超时
├── 常见于慢 API(数据库查询、外部调用)
└── 排查: 检查源站响应时间、增大 CDN 超时配置
520-530 (Cloudflare 专用)
520: 源站返回了未知错误
521: 源站拒绝连接(Web Server Down)
522: 连接超时
523: 源站不可达
524: 超时(A Timeout Occurred)
525: SSL 握手失败
526: 无效的 SSL 证书
3.3 回源流量突增(Origin Stampede)
当缓存大规模过期或被 Purge 后,所有请求同时回源,可能压垮源站:
缓存雪崩场景:
T=0: 热门内容被 Purge
T=0.1s: 1000 个并发请求同时回源
↓
源站过载 → 响应变慢 → CDN 超时
↓
更多请求回源(恶性循环)
防护机制:
1. Request Collapsing(请求合并)
┌──────────────────────────────────────────────┐
│ 100 个请求同时 MISS │
│ → 只有 1 个请求回源 │
│ → 其他 99 个等待这个请求的响应 │
│ → 回源成功后,99 个请求从缓存返回 │
└──────────────────────────────────────────────┘
2. Stale-While-Revalidate
→ 缓存过期后继续返回旧内容
→ 后台异步回源刷新
→ 源站零压力
3. Origin Shield(回源保护层)
→ 所有 PoP 的回源请求先到 Shield
→ Shield 做 Request Collapsing
→ 源站只看到 Shield 的请求
Nginx 配置 Request Collapsing:
# proxy_cache_lock — 对同一资源的并发回源请求排队
proxy_cache_lock on;
proxy_cache_lock_timeout 5s; # 等待锁的超时
proxy_cache_lock_age 5s; # 锁的最大持有时间
# 超过 lock_timeout 后的行为:
# - 超时的请求也回源(防止单个慢请求阻塞所有人)
# - proxy_cache_lock_age 后自动释放锁
# stale-while-revalidate 支持
proxy_cache_use_stale updating; # 更新时返回旧内容
proxy_cache_background_update on; # 后台异步更新
# 完整的缓存配置
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=cdn_cache:100m
max_size=10g
inactive=7d
use_temp_path=off;
server {
location / {
proxy_cache cdn_cache;
proxy_cache_valid 200 1h;
proxy_cache_valid 404 1m;
# Request Collapsing
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
# Stale content
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# 添加调试头
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Key $scheme$proxy_host$request_uri;
proxy_pass http://origin;
}
}
四、内容不一致问题
用户报告”我看到的是旧内容”或”不同地区看到不同版本”——这是 CDN 缓存一致性问题。
4.1 诊断步骤
#!/bin/bash
# cdn-consistency-check.sh — 检查 CDN 内容一致性
URL="${1:-https://www.example.com/page}"
echo "=== CDN Content Consistency Check ==="
echo "URL: $URL"
echo ""
# 1. 获取源站原始内容的 ETag/Last-Modified
echo "--- Origin Response ---"
origin_headers=$(curl -sI -H "Cache-Control: no-cache" "$URL")
origin_etag=$(echo "$origin_headers" | grep -i "etag" | awk '{print $2}' | tr -d '\r"')
origin_lm=$(echo "$origin_headers" | grep -i "last-modified" | cut -d: -f2- | tr -d '\r')
origin_cl=$(echo "$origin_headers" | grep -i "content-length" | awk '{print $2}' | tr -d '\r')
echo " ETag: $origin_etag"
echo " Last-Modified: $origin_lm"
echo " Content-Length: $origin_cl"
echo ""
# 2. 多次请求检查缓存一致性
echo "--- Cache Consistency (10 requests) ---"
for i in $(seq 1 10); do
resp=$(curl -sI "$URL")
etag=$(echo "$resp" | grep -i "etag" | awk '{print $2}' | tr -d '\r"')
age=$(echo "$resp" | grep -i "^age:" | awk '{print $2}' | tr -d '\r')
cache=$(echo "$resp" | grep -i "cf-cache-status\|x-cache" | head -1 | awk '{print $2}' | tr -d '\r')
pop=$(echo "$resp" | grep -i "cf-ray\|x-served-by\|x-amz-cf-pop" | head -1 | tr -d '\r')
cl=$(echo "$resp" | grep -i "content-length" | awk '{print $2}' | tr -d '\r')
match="✓"
if [ "$etag" != "$origin_etag" ] && [ -n "$origin_etag" ] && [ -n "$etag" ]; then
match="✗ ETag MISMATCH"
fi
if [ "$cl" != "$origin_cl" ] && [ -n "$origin_cl" ] && [ -n "$cl" ]; then
match="✗ Size MISMATCH"
fi
printf " #%02d cache=%-12s age=%-6s etag=%-20s size=%-8s %s\n" \
"$i" "$cache" "${age:-N/A}" "${etag:-N/A}" "${cl:-N/A}" "$match"
sleep 0.3
done4.2 常见不一致原因
不一致原因及修复:
1. Purge 未完全传播
原因: Purge 请求发出后,部分 PoP 还未收到
表现: 不同地区用户看到不同版本
修复: 等待传播完成(通常 2-30 秒),或使用版本化 URL
2. 源站返回不一致
原因: 源站多实例部署时内容未同步
表现: 相同 URL 有时返回新内容有时返回旧内容
排查: 多次请求源站检查响应是否一致
修复: 确保源站部署原子性
3. Cache Key 设计问题
原因: 相同内容的 URL 被视为不同 Cache Key
表现: 命中率低,多次请求结果不一致
修复: 规范化 Cache Key(排序/过滤 Query String)
4. Vary 头导致多变体
原因: Vary: Accept-Language 导致每种语言单独缓存
表现: 同一 URL 在不同浏览器设置下内容不同(符合预期)
修复: 确认是否需要变体缓存,不需要则移除 Vary
5. stale-while-revalidate 窗口
原因: 缓存过期后返回旧内容同时后台刷新
表现: 短暂看到旧内容,刷新后变新
修复: 这是预期行为,减少 SWR 窗口可缩短不一致时间
4.3 使用版本化 URL 避免一致性问题
版本化 URL 策略:
方案 1: 文件名哈希
/static/app.js → /static/app.a1b2c3d4.js
/static/style.css → /static/style.e5f6g7h8.css
+ 可以设置超长 TTL(1 年)
+ 内容更新时 URL 变化,自动绕过缓存
- 需要构建工具支持
方案 2: Query String 版本
/static/app.js?v=1.2.3
+ 实现简单
- 某些 CDN 不缓存带 Query String 的 URL
- 中间代理可能忽略 Query String
方案 3: 路径版本
/v1.2.3/static/app.js
+ CDN 友好
+ 可以同时保留多个版本
- 需要服务端路由支持
推荐: 方案 1(文件名哈希)用于静态资源
方案 3(路径版本)用于 API
五、CDN 性能诊断
5.1 延迟分解
# 完整的 CDN 性能诊断
$ curl -o /dev/null -s -w @- https://www.example.com/ << 'EOF'
==== CDN Performance Breakdown ====
DNS Lookup: %{time_namelookup}s
TCP Connect: %{time_connect}s
TLS Handshake: %{time_appconnect}s
TTFB: %{time_starttransfer}s
Total: %{time_total}s
Download Size: %{size_download} bytes
Download Speed: %{speed_download} bytes/s
Remote IP: %{remote_ip}
HTTP Code: %{http_code}
=====================================
EOF
# 解读指标:
# DNS Lookup: > 50ms → DNS 解析慢,检查 DNS 配置
# TCP Connect: > 100ms → 到 CDN PoP 距离远,检查 Anycast
# TLS Handshake: > 200ms → TLS 配置不优,检查 TLS 版本/OCSP
# TTFB: > 500ms → 可能回源了,检查缓存状态
# Total: > 1s → 整体偏慢,需要逐项分析5.2 多地域性能对比
#!/bin/bash
# cdn-performance-map.sh — 多地域 CDN 性能测试
URL="${1:-https://www.example.com/}"
# 使用不同 DNS 解析器模拟不同地区
declare -A RESOLVERS=(
["Cloudflare"]="1.1.1.1"
["Google"]="8.8.8.8"
["Quad9"]="9.9.9.9"
)
echo "=== Multi-Region CDN Performance ==="
echo "URL: $URL"
echo ""
printf "%-15s %-15s %-8s %-8s %-8s %-8s %-8s %-15s\n" \
"Resolver" "IP" "DNS" "TCP" "TLS" "TTFB" "Total" "Cache"
for name in "${!RESOLVERS[@]}"; do
resolver=${RESOLVERS[$name]}
result=$(curl -o /dev/null -s --resolve "$(echo $URL | awk -F/ '{print $3}'):443:$(dig +short @$resolver $(echo $URL | awk -F/ '{print $3}') | head -1)" \
-w "%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total} %{remote_ip}" \
"$URL" 2>/dev/null)
cache=$(curl -sI "$URL" 2>/dev/null | grep -i "cf-cache-status\|x-cache" | head -1 | awk '{print $2}' | tr -d '\r')
read dns tcp tls ttfb total ip <<< "$result"
printf "%-15s %-15s %-8s %-8s %-8s %-8s %-8s %-15s\n" \
"$name" "$ip" \
"$(echo "$dns * 1000" | bc)ms" \
"$(echo "$tcp * 1000" | bc)ms" \
"$(echo "$tls * 1000" | bc)ms" \
"$(echo "$ttfb * 1000" | bc)ms" \
"$(echo "$total * 1000" | bc)ms" \
"${cache:-N/A}"
done5.3 CDN 日志分析
大多数 CDN 提供商支持实时日志或日志推送,用于事后分析:
CDN 日志关键字段:
字段 │ 用途
────────────────────────┼───────────────────────────────
timestamp │ 请求时间
client_ip │ 用户 IP(或代理 IP)
edge_pop │ 处理请求的边缘节点
cache_status │ HIT/MISS/BYPASS/EXPIRED
origin_response_time │ 回源耗时(ms)
total_time │ 请求总耗时(ms)
status_code │ HTTP 状态码
request_url │ 请求 URL
content_type │ 响应内容类型
bytes_sent │ 响应大小
tls_version │ TLS 版本
http_protocol │ HTTP/1.1 或 HTTP/2
user_agent │ 客户端标识
分析脚本示例:
# 从 CDN 日志中分析缓存命中率(假设日志格式已解析为 TSV)
# 字段: timestamp client_ip cache_status status_code url edge_pop ttfb
# 1. 缓存命中率
echo "=== Cache Hit Ratio ==="
awk -F'\t' '{stats[$3]++; total++} END {
for (s in stats) printf " %-15s %8d (%5.1f%%)\n", s, stats[s], stats[s]/total*100
}' cdn_access.log
# 2. 回源慢请求 Top 10
echo "=== Slowest Origin Requests ==="
awk -F'\t' '$3 == "MISS" && $7 > 1000 {print $7"ms\t"$5}' cdn_access.log | \
sort -rn | head -10
# 3. 5xx 错误的 URL 分布
echo "=== 5xx Errors by URL ==="
awk -F'\t' '$4 >= 500 {urls[$5]++} END {
for (u in urls) print urls[u], u
}' cdn_access.log | sort -rn | head -20
# 4. 各 PoP 节点的命中率
echo "=== Cache Hit Ratio by PoP ==="
awk -F'\t' '{
pop_total[$6]++
if ($3 == "HIT" || $3 == "STALE" || $3 == "REVALIDATED")
pop_hit[$6]++
} END {
for (p in pop_total) {
hit = pop_hit[p]+0
ratio = hit / pop_total[p] * 100
printf " %-20s %8d total, %5.1f%% hit\n", p, pop_total[p], ratio
}
}' cdn_access.log | sort -t',' -k2 -rn六、CDN 调试工具集
6.1 命令行工具速查
# === CDN 调试命令速查表 ===
# 1. 查看完整响应头
curl -sI https://www.example.com/
# 2. 跳过缓存直接回源
curl -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://www.example.com/
# 3. 查看请求经过的所有代理
curl -sI https://www.example.com/ | grep -i via
# 4. 检查特定 CDN 的 PoP 节点
curl -sI https://www.example.com/ | grep -i "cf-ray\|x-served-by\|x-amz-cf-pop"
# 5. 测试特定 PoP 节点(通过 DNS 解析到特定 IP)
curl -sI --resolve www.example.com:443:104.16.1.1 https://www.example.com/
# 6. 查看 CDN 解析结果
dig www.example.com +short
dig www.example.com +trace
# 7. 查看 CNAME 链
dig www.example.com CNAME +short
# www.example.com.cdn.cloudflare.net.
# 8. HTTP/2 帧级别调试
curl -v --http2 https://www.example.com/ 2>&1 | grep -E "^[<>*]"
# 9. TLS 握手详情
openssl s_client -servername www.example.com \
-connect www.example.com:443 -brief
# 10. 测量 CDN vs 源站的性能差异
echo "CDN:"
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s Total: %{time_total}s\n" \
https://www.example.com/
echo "Origin (bypass CDN):"
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s Total: %{time_total}s\n" \
https://origin.example.com/6.2 排查决策树
CDN 问题排查决策树:
用户报告"页面加载慢"
├── 检查 CDN 缓存状态
│ ├── HIT → 不是 CDN 缓存问题
│ │ ├── 检查 Age 值(是否很大 → 内容可能过旧)
│ │ └── 检查传输延迟(DNS/TCP/TLS)
│ ├── MISS → 缓存未命中
│ │ ├── 检查 Cache-Control 头
│ │ ├── 检查 Query String 是否导致碎片化
│ │ └── 检查 Origin Shield 是否启用
│ ├── BYPASS → 缓存被跳过
│ │ ├── 检查是否有 Cookie 导致 Bypass
│ │ ├── 检查 CDN 规则配置
│ │ └── 检查请求头中是否有 Authorization
│ └── DYNAMIC → 被标记为动态
│ ├── 检查 Content-Type
│ └── 在 CDN 规则中显式启用缓存
用户报告"看到旧内容"
├── 检查 Age 值(过大说明缓存未更新)
├── 检查是否发起了 Purge
├── 检查 Purge 是否传播到所有 PoP
└── 检查源站是否返回了一致的内容
用户报告"页面报错"
├── 检查 HTTP 状态码
│ ├── 502/504 → 回源异常
│ │ ├── 检查源站是否运行
│ │ ├── 检查回源连接(TCP/TLS)
│ │ └── 检查回源超时配置
│ ├── 520-530 → Cloudflare 专用错误
│ │ └── 参考 Cloudflare 错误码文档
│ └── 403 → 可能被 CDN WAF 拦截
│ └── 检查 WAF 规则和日志
└── 检查是否是特定地区/PoP 的问题
└── 从多个地区测试对比
七、总结
CDN 故障调试的核心方法论:
- 读响应头:
X-Cache、Age、Via、CF-Ray等头部是 CDN 状态的第一手信息 - 测缓存命中率:CHR 是 CDN 效果的核心指标,低于 80% 需要排查 Cache Key 和 Cache-Control
- 查回源链路:回源超时和 5xx 是最严重的问题,需要同时排查 CDN 配置和源站状态
- 建监控体系:Prometheus + 告警,覆盖命中率、回源错误率、延迟 P99
上一篇: CDN 与 HTTPS:边缘 TLS、证书管理与安全 下一篇: Socket 编程模型演进:从阻塞到多路复用
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】CDN 架构原理:PoP、边缘节点与 Origin Shield
系统解剖 CDN 的多层缓存架构——从 DNS 调度到 PoP 内部结构、Origin Shield 回源保护、多 CDN 部署策略。结合实际配置和响应头分析,给出 CDN 架构的工程理解。
网络工程索引
汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。
【网络工程】CDN 缓存策略:TTL、Purge 与 stale-while-revalidate
深入剖析 CDN 缓存策略的工程实践——TTL 设置方法论、Purge 机制与一致性保证、stale-while-revalidate 的工程价值、缓存命中率优化与常见缓存问题排查。
【网络工程】动态加速:TCP 优化、路由优化与边缘计算
CDN 对静态资源的加速已成共识,但动态 API 请求同样可以受益于 CDN 的网络基础设施。本文从 TCP 优化、智能路由到边缘计算三个层次,拆解动态加速的技术原理、架构选型与工程实践。