2024年11月05日8 分钟
CDN 原理与实践:从用户请求到内容分发的全过程
深入理解 CDN 的工作原理,涵盖 DNS 调度、边缘节点、缓存策略和前端项目实战配置
什么是 CDN
CDN(Content Delivery Network,内容分发网络)是一组分布在全球各地的服务器,它们缓存你网站的静态资源,让用户从最近的节点获取内容,而不是从遥远的源服务器获取。
没有 CDN:
北京用户 ──────────── 跨太平洋 ──────────── 美国源服务器
延迟: 200-400ms
有 CDN:
北京用户 ──── 北京 CDN 节点(缓存副本)
延迟: 5-20ms
CDN 请求的完整流程
用户输入 https://cdn.example.com/bundle.js
│
┌─────▼──────┐
│ 浏览器 DNS │
│ 解析请求 │
└─────┬──────┘
│
┌─────▼──────┐
│ 本地 DNS │ 查询 cdn.example.com
│ (ISP递归) │
└─────┬──────┘
│
┌─────▼──────────────┐
│ 权威 DNS 返回 CNAME │ cdn.example.com → cdn.provider.net
└─────┬──────────────┘
│
┌─────▼──────────────┐
│ CDN 智能 DNS 调度 │ 根据用户 IP 判断地理位置
│ │ 选择最优节点
│ 考虑因素: │
│ - 地理距离 │
│ - 节点负载 │
│ - 网络质量 │
│ - 节点健康状态 │
└─────┬──────────────┘
│ 返回最优节点 IP: 1.2.3.4
▼
┌──────────────┐
│ 浏览器请求 │ GET /bundle.js → 1.2.3.4(北京边缘节点)
│ 边缘节点 │
└──────┬───────┘
│
┌─────▼─────┐
│ 缓存命中? │
├─── 是 ────▶ 直接返回缓存(5ms)✅
│ │
└─── 否 ────▶ 回源请求
│
┌─────▼────────┐
│ 中间层缓存 │ 区域中心节点
│ 命中? │
├── 是 ────────▶ 返回 + 缓存到边缘
│ │
└── 否 ────────▶ 请求源服务器
│
┌─────▼────┐
│ 源服务器 │
│ 返回资源 │
└─────┬────┘
│
逐级缓存回填
源 → 中间层 → 边缘节点
│
▼
返回给用户 ✅
CDN 的核心机制
1. DNS 调度(智能选路)
用户在北京请求 static.example.com:
CDN DNS 系统收到请求:
用户 IP: 202.106.x.x(北京联通)
│
├─ 查地理数据库 → 北京
├─ 查北京节点列表:
│ 节点A(联通): 负载 40%,延迟 5ms ← 最优
│ 节点B(电信): 负载 20%,延迟 15ms
│ 节点C(移动): 负载 60%,延迟 12ms
│
└─ 返回节点A 的 IP
结果:北京联通用户 → 北京联通 CDN 节点(同运营商,最快)
2. 缓存策略
HTTP 缓存头控制 CDN 行为:
Cache-Control: public, max-age=31536000
│ │ │
│ │ └── 缓存 1 年(365 天)
│ └── 可以被 CDN 缓存
└── 缓存控制头
Cache-Control: no-cache
└── 每次都要向源服务器验证(但可以缓存)
Cache-Control: no-store
└── 完全不缓存(CDN 直接透传)
3. 缓存层级
┌─────────────┐
│ 源服务器 │ 存储原始文件
└──────┬──────┘
│
┌─────────┼─────────┐
│ │ │
┌─────▼───┐ ┌───▼────┐ ┌──▼──────┐
│ 华北中心 │ │华东中心 │ │ 华南中心 │ L2 缓存(区域)
└────┬────┘ └───┬────┘ └────┬────┘
│ │ │
┌─────┼────┐ │ ┌────┼─────┐
│ │ │ │ │ │ │
┌─▼─┐ ┌▼─┐ ┌▼─┐ ┌▼─┐ ┌▼─┐ ┌▼─┐ ┌▼──┐
│北京│ │天│ │济│ │上│ │广│ │深│ │成都│ L1 边缘节点
└───┘ │津│ │南│ │海│ │州│ │圳│ └───┘
└──┘ └──┘ └──┘ └──┘ └──┘
边缘节点没有 → 找区域中心
区域中心没有 → 找源服务器
找到后逐级缓存回填
前端项目的 CDN 配置
Next.js + Vercel
// next.config.js
module.exports = {
// Vercel 自动为静态资源启用 CDN
// _next/static/ 下的文件自动缓存 1 年
// 自定义 CDN 域名
assetPrefix: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com'
: '',
// 图片 CDN
images: {
domains: ['cdn.example.com'],
loader: 'custom',
loaderFile: './lib/image-loader.js',
},
}
缓存策略配置
// next.config.js — headers 配置
module.exports = {
async headers() {
return [
{
// 带 hash 的静态资源:缓存 1 年
source: '/_next/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
// HTML 页面:每次检查更新
source: '/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=0, must-revalidate',
},
],
},
{
// API 接口:不缓存
source: '/api/:path*',
headers: [
{
key: 'Cache-Control',
value: 'no-store',
},
],
},
]
},
}
文件名 Hash 策略
为什么静态资源可以缓存 1 年?
因为文件名包含内容 hash:
bundle.a1b2c3d4.js ← 内容变化 → hash 变化 → 文件名变化 → 新的 URL
旧版本:bundle.a1b2c3d4.js(缓存 1 年,不影响)
新版本:bundle.e5f6g7h8.js(全新 URL,CDN 重新获取)
这就是为什么 Next.js 的 _next/static 文件可以设置超长缓存
HTML 引用的是新文件名,自然加载新版本
CDN 的缓存失效
主动刷新
# 使用 CDN 提供商的 API 主动清除缓存
# Cloudflare 示例
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {api_token}" \
-d '{"files":["https://example.com/styles.css"]}'
# 全量刷新(谨慎使用,会导致所有节点回源)
curl -X POST ... -d '{"purge_everything":true}'
版本化 URL
最佳实践:不清除缓存,而是使用新 URL
v1: https://cdn.example.com/app.v1.js
v2: https://cdn.example.com/app.v2.js
旧版本继续缓存(不影响还在使用的用户)
新版本自动从源站获取
CDN 常见问题
缓存不一致
问题:不同节点缓存了不同版本
北京节点:bundle.js(v2,刚刷新)
上海节点:bundle.js(v1,还没过期)
解决:
1. 使用文件名 hash → 不同版本就是不同文件
2. 缓存 purge API → 全节点刷新
3. 合理设置 TTL → 短的缓存时间用于频繁变更的资源
回源风暴
问题:缓存同时过期 → 大量请求涌向源服务器
1000 个请求同时到达 → 缓存都过期
→ 1000 个请求全部回源 → 源服务器崩溃
解决:
1. 缓存预热:上线前主动推送到 CDN 节点
2. 回源合并:相同 URL 的回源请求只发一个
3. 过期时间随机化:避免同时过期
CDN 选型对比
| CDN 服务 | 特点 | 适用场景 |
|---|---|---|
| Cloudflare | 免费额度大,全球节点 | 个人网站、中小项目 |
| Vercel Edge | Next.js 原生集成 | Next.js 项目 |
| AWS CloudFront | 与 AWS 生态集成 | 已用 AWS 的项目 |
| 阿里云 CDN | 国内节点多 | 国内用户为主 |
| 腾讯云 CDN | 国内节点多 | 国内用户为主 |
总结
CDN 的核心价值:
- 智能 DNS 调度:把用户导向最近、最快的节点
- 分级缓存:边缘 → 区域 → 源站,减少回源
- 缓存策略:Hash 文件名 + 长期缓存 = 最佳方案
- 前端实践:静态资源长缓存 + HTML 短缓存/不缓存