内容分发网络(CDN)是一个网站无论你从东京还是多伦多打开都觉得很快的原因。它是一大批分布在全球的缓存服务器,把你的内容存在物理上离用户很近的地方,这样请求由附近的边缘(edge)返回,而不必跨越大洋去你的源站。CDN 几乎出现在本站每一篇系统设计里——Drive、视频流、Pastebin——作为吸收读流量的那一层。下面讲它到底怎么工作。
- 两大收益:延迟和卸载——内容由附近边缘返回更快(距离更短),而且大多数请求根本到不了源站(负载更小)。
- 边缘 PoP + anycast/GeoDNS 自动把每个用户路由到最近的接入点。
- 缓存行为由 HTTP 控制——
Cache-Control/TTL 决定什么可缓存、缓存多久;缓存键决定什么算"同一个对象"。 - 命中 vs 未命中——命中由边缘返回;未命中从源站拉取、缓存后再返回。
- 拉(惰性)vs 推——拉式 CDN 在首次请求时拉取;推式 CDN 预先上传内容。
- 失效是最难的部分——用 TTL、显式清除(purge),或带版本/指纹的 URL(cache busting)。
- 附带:安全——CDN 吸收 DDoS、在边缘终结 TLS、托管 WAF。
CDN 把你的内容缓存在全球边缘服务器上,并把每个用户路由到最近的那个(anycast/GeoDNS),从而降低延迟、为源站挡流量。HTTP 的 Cache-Control 头和缓存键管理缓存行为;缓存命中由边缘返回,未命中从源站拉取并缓存。难题是失效——用 TTL、purge,或带指纹的 URL 解决。静态资源极易缓存;动态内容靠边缘计算和路由优化。
为什么用 CDN:延迟与卸载
两条物理与经济事实驱动整个设计。第一,距离即延迟:数据以有限速度传播,所以从悉尼到弗吉尼亚某服务器的往返本质就慢,无论那台服务器多快。把一份副本放在离用户 20 公里而非 16000 公里处,能大幅砍掉往返时间。第二,卸载:如果边缘从缓存返回大多数请求,你的源站只看到一小部分流量——一个爆火的页面或热门视频几乎全由边缘服务,源站不会被打垮。给用户降延迟,给源站保命——这就是卖点。
怎么工作:边缘 PoP 与路由
CDN 在全球各数据中心运营接入点(PoP,points of presence)——一簇簇缓存服务器。诀窍是自动把每个用户带到最近的 PoP,主要靠两种方式:anycast(很多 PoP 宣告同一个 IP 地址,互联网路由自然把用户送到拓扑上最近的那个)和 GeoDNS(DNS 根据解析方的位置把 CDN 主机名解析到不同的边缘 IP——见我们的 DNS 文章)。无论哪种,用户都在不知不觉中连到了一个近的边缘。
用户 → 最近的边缘 PoP
├─ 命中 → 从边缘缓存返回 (快,不碰源站)
└─ 未命中 → 从源站拉取 → 缓存 → 返回
(该地区后续请求就都是命中了)
目标:最大化命中率 → 大多数流量在边缘返回
缓存行为:控制、TTL 与键
边缘缓存什么、缓存多久,由源站发出的 HTTP 头控制。Cache-Control: max-age=3600 表示"可缓存一小时";no-store 表示"绝不缓存";stale-while-revalidate 让边缘在后台刷新时先返回稍旧的副本——很适合保持响应快速。
Cache-Control: public, max-age=31536000, immutable # 带指纹的资源,缓存 1 年
Cache-Control: public, max-age=60, stale-while-revalidate=300
Cache-Control: no-store # 绝不缓存(用户专属)
同样重要的是缓存键(cache key)——CDN 用它判断两个请求是不是"同一个对象"。默认是 URL,但可以包含查询串、头或 cookie。搞错了,要么缓存太粗(返回错的变体),要么太细(每个请求都是唯一键 → 命中率趋近于零)。缓存键基数过高是经典的 CDN 自坑。
推式 vs 拉式 CDN
| 方面 | 拉式 CDN | 推式 CDN |
|---|---|---|
| 内容如何到达 | 惰性,首次请求时(未命中→源站) | 你上传/预加载到 CDN |
| 首次请求 | 较慢(缓存未命中) | 快(已在边缘) |
| 最适合 | 大型、常变的目录 | 大文件、可预测的热门内容 |
| 工作量 | 低——把 DNS 指向 CDN 即可 | 较高——管理上传/过期 |
拉式是常见默认(配好就不用管);推式适合你确定会火、又不想首次未命中变慢的大媒体。
失效:最难的部分
"计算机科学只有两件难事:缓存失效和命名。"内容一旦缓存在几百个边缘,更新它就真的很棘手。三种策略,精确度递增:
- TTL 过期——等
max-age到期。简单,但你要容忍最长一个 TTL 的陈旧。 - 显式清除(purge)——让 CDN 立刻驱逐某个 URL(或标签/前缀)。精确,但在所有边缘的传播不是瞬时的。
- 带版本/指纹的 URL(cache busting)——静态资源的最佳模式:把内容哈希放进文件名(
app.3f9a1c.js),永久缓存它;一改动就产生新 URL,根本没有要失效的东西。
对静态资源,组合用immutable + 指纹 URL:把 app.<hash>.js 缓存一年,从一个短 TTL 的 HTML 页面引用新的哈希。你拿到了最大化缓存且零失效——发布只是指向新文件名。这正是构建工具给资源名加哈希的原因。
静态 vs 动态内容
静态内容——图片、CSS/JS、视频分片、下载——是 CDN 的看家本领:它对所有人都一样、可长时间缓存。动态内容(个性化页面、API 响应)更难,因为因人而异,但 CDN 仍能帮忙,靠动态加速:保持从边缘到源站的预热、优化过的连接(TLS 已协商、走 CDN 骨干的更优路由),以及越来越多的边缘计算——在 PoP 上跑代码来个性化、拼装或缓存片段,离用户更近。"CDN" 和"边缘平台"的界限正因此变得模糊。
安全附带收益
因为 CDN 挡在你源站前面、吸收所有入站流量,它天然是一道安全层:它用庞大容量在攻击到达你之前吸收 DDoS、在边缘终结 TLS(离用户更近、握手更快),还常托管 WAF(Web 应用防火墙)和机器人缓解。对很多站点,光是 DDoS 防护就足以证明上 CDN 的价值。
用途与常见坑
- 静态资源与媒体分发——经典款;源站搭 对象存储。
- 视频流——把分片缓存在边缘以扩展(见视频流设计)。
- 坑——缓存击穿(cache stampede):一个热门对象过期时很多边缘同时未命中、一起打源站;用错峰 TTL、
stale-while-revalidate或源站请求合并缓解。 - 坑——缓存了不该缓存的:缓存个性化或带鉴权的响应会在用户间泄露数据;标
private/no-store并谨慎设键。 - 坑——命中率低:缓存键基数过高(键里含了不必要的查询串/cookie)。
CDN 用一点复杂度(缓存头、失效)换两个大收益:更低延迟(内容离用户近)和源站卸载(大多数请求到不了你)。用 anycast/GeoDNS 把用户路由到最近边缘,用 Cache-Control 和好的缓存键控制缓存,用指纹 immutable URL 绕开失效,顺便也把安全交给边缘。
CDN 给你什么?更低延迟(从附近边缘返回)和源站卸载(大多数读由缓存服务,源站扛得住峰值)。
用户怎么被路由到最近边缘?anycast(很多 PoP 共享一个 IP,路由选最近的)或 GeoDNS(DNS 返回区域专属的边缘 IP)。
推式 vs 拉式 CDN?拉式首次请求时惰性缓存(简单、首次未命中慢);推式预加载内容(首次即命中、管理更多)。
怎么让缓存内容失效?TTL 过期、显式 purge,或——静态最佳——指纹 immutable URL,改动即新 URL、无需失效。
什么是缓存击穿?热门对象过期时很多边缘同时未命中压垮源站;用 stale-while-revalidate、抖动 TTL 或请求合并修复。