深蓝黑背景封面:左侧原创示意图——一条请求路径:用户先到「调度系统」拿到最近边缘节点的地址,再访问高亮描边的「边缘节点」;命中时青色实线直接返回内容,未命中时紫色虚线「回源」连向右下的「源站」取回内容并缓存。右侧大标题「CDN 靠什么给静态资源提速」,副标题「就近调度 + 边缘缓存 + 回源兜底」;左上标注「笔试复盘 · 网络」,右上圆角胶囊标注「Day 09 · 11-16」 图 1:本文封面,命中与回源两条路径。

先说明属性:这是一篇历史笔试题的复盘,题目出自本地历史笔试资料,与任何公司的当前招聘无关。「什么是 CDN」看起来像送分题,但能答到第二层——调度怎么把用户导到最近的节点、缓存过期后会发生什么——才算真懂。这道题拆开就是四块组件加两条路径。

CDN 是什么:把内容搬到离用户近的地方。

没有 CDN 时,全国用户都直接访问你的源站:离得远的时延高,热门资源还把源站带宽打满。CDN(内容分发网络)的思路是在各地部署边缘节点,把图片、JS、CSS、安装包这类静态资源预先或按需缓存到离用户近的节点上。IETF 的 CDN 互联框架文档(RFC 7336)把直接给用户递内容的节点称为 surrogate,我们平时就叫它边缘节点。整个体系四块组件:调度系统负责选节点,边缘节点负责缓存与分发,回源链路负责兜底,源站是内容的权威来源。

调度:用户怎么找到「最近的节点」。

最常用的调度方式是 DNS 重定向。你的资源域名通过 CNAME 指到 CDN 服务商的调度域,用户解析域名时,CDN 的权威 DNS 根据来访解析器的网络位置,返回一个离用户近、负载又合适的边缘节点 IP——同一个域名,不同城市的用户解析出不同地址。RFC 7336 把这类请求路由分为 DNS 重定向与 HTTP 重定向两种基本形态:前者在解析阶段完成、用户全程无感知,后者先连上节点再 302 跳转。DNS 解析本身的全过程这里不展开,记住调度发生在「域名变成 IP」这一步就够了。

命中与回源:边缘节点的缓存账本。

请求到了边缘节点,只有两种结局。命中:缓存里有这份内容且还没过期,直接返回,时延低、源站完全无感知。未命中:节点回源拉取内容,写进本地缓存,再返回给用户——第一个访问的用户慢一点,后面的都快。缓存能存多久由源站说了算:HTTP 缓存规范(RFC 9111)里,响应头 Cache-Control: max-age=30 就是源站在声明「这份副本 30 秒内是新鲜的」,过期之后节点要回源重新拉取或验证。这正好解释了那个经典问题:为什么你更新了资源,用户端还是旧版——因为旧副本还没过期。

用一段 Node.js 脚本模拟这个账本:

// 模拟 CDN 边缘节点的缓存行为:命中直接返回,未命中回源并写入缓存,max-age 到期后再次回源
const MAX_AGE = 30; // 秒,来自源站响应的 Cache-Control: max-age=30
let cache = null;   // { content, storedAt }
let originHits = 0;

function origin() {           // 源站:内容的权威来源
  originHits++;
  return { content: 'app.v3.js', headers: { 'Cache-Control': 'max-age=30' } };
}

function request(t) {         // t 秒时用户请求到达边缘节点
  const fresh = cache && (t - cache.storedAt) < MAX_AGE;
  if (fresh) {
    return `t=${String(t).padStart(3)}s 命中缓存(已存 ${t - cache.storedAt}s,未过期)→ 直接返回,时延低,源站无感知`;
  }
  const res = origin();       // 未命中或已过期:回源
  cache = { content: res.content, storedAt: t };
  return `t=${String(t).padStart(3)}s ${originHits === 1 ? '未命中' : '已过期'} → 回源拉取(第 ${originHits} 次),写入缓存后返回`;
}

for (const t of [0, 5, 12, 25, 35, 60, 65]) console.log(request(t));
console.log(`---\n7 次请求只回源 ${originHits} 次,其余全部由边缘节点消化`);

实测输出:

t=  0s 未命中 → 回源拉取(第 1 次),写入缓存后返回
t=  5s 命中缓存(已存 5s,未过期)→ 直接返回,时延低,源站无感知
t= 12s 命中缓存(已存 12s,未过期)→ 直接返回,时延低,源站无感知
t= 25s 命中缓存(已存 25s,未过期)→ 直接返回,时延低,源站无感知
t= 35s 已过期 → 回源拉取(第 2 次),写入缓存后返回
t= 60s 命中缓存(已存 25s,未过期)→ 直接返回,时延低,源站无感知
t= 65s 已过期 → 回源拉取(第 3 次),写入缓存后返回
---
7 次请求只回源 3 次,其余全部由边缘节点消化

七次请求只回源三次,其余全被边缘节点消化——这就是 CDN 给源站卸压的直观账本。注意 t=60 时命中的是 35 秒时写入的副本,副本年龄 25 秒仍未过期。

深蓝黑背景概念图,中部是一条从左到右的完整请求链路。最左是「用户」,向右第一步到「调度系统」:基于 DNS 把用户解析到离他最近的边缘节点。第二步进入画面中央最大的「边缘节点」卡片,卡片内部画一块缓存区:命中时青色路径直接从缓存取内容返回用户,标注「时延低、源站无感知」;未命中时紫色路径向下连到最右侧「源站」拉取内容,写入缓存后再返回,标注「首次慢,之后命中」。链路下方一条注释带说明缓存有效期:源站用 Cache-Control 等头部告诉节点内容能存多久,到期前用户拿到的是旧副本——这解释了「资源更新了用户端还是旧版」。底部四枚组件卡:调度系统负责选节点、边缘节点负责缓存与分发、回源链路负责兜底、源站是内容的权威来源 图 2:调度、命中、回源与过期的完整链路。

CDN 不解决什么,以及怎么答完整。

两个边界要心里有数。其一,CDN 主要服务静态与可缓存内容;个性化动态接口没法缓存,CDN 最多帮你优化传输链路,省不掉源站的计算。其二,「等过期」之外还有主动手段:发布新版本后通过控制台刷新(purge)让节点提前作废指定内容,或者在大促前做预热,把内容提前推到节点上——CDN 互联标准里的触发接口(RFC 8007)定义的正是这类预置与清除动作。工程上还常见更省事的办法:给静态资源文件名加内容哈希,新版本就是新 URL,天然绕开旧副本。

回到笔试现场,答题结构建议:先一句话定义——CDN 是把内容缓存到离用户最近的节点、就近分发的分布式缓存网络;再画出四块组件与「命中、回源」两条路径;然后用 Cache-Control 过期机制解释「更新了还是旧版」;最后补刷新、预热与文件名哈希。定义是及格线,回源账本和过期机制是区分度。你排查过「资源更新不生效」的问题吗,最后是等过期、手动刷新,还是给文件改了版本号?欢迎分享你的做法。