关于 CDN、回源等问题一网打尽
Category(分类): Other Status: 未知
本文保留原文关于 CDN 访问流程、回源、API 缓存、资源过期和专业术语的结构,并结合 HTTP 缓存规范和当前 CDN 产品实践修订容易误解的说法。
原文发表于较早时期,文中的全局负载均衡、区域负载均衡和 CDN 节点图是典型的教学模型。现代 CDN 可能使用 DNS 调度、Anycast、分层缓存、边缘函数或多种机制组合,并不一定完全按图中的设备名称实现。
复盘日常问题时,看到了曾经听到后端同学讨论“回源”的场景。一直以来我对 CDN 相关知识也一知半解,借此机会把访问流程、缓存、回源和动态内容重新梳理一遍。
文章目录
- 访问 CDN 资源和不通过 CDN 访问的过程有什么不同;
- 回源是什么意思,什么时候会回源;
- 除了静态资源,API 是否可以缓存;
- 资源的新鲜度如何判定,CDN 如何更新数据;
- CDN 常见专业术语和排查方法。
一、访问 CDN 资源和不通过 CDN 访问的过程有什么不同
不通过 CDN 的简化过程
用户访问源站时,通常是:
浏览器/系统缓存
-> stub resolver
-> 递归 DNS 解析器
-> 解析 A/AAAA/CNAME 等记录
-> 与源站建立 TCP/TLS 或 QUIC 连接
-> 源站处理 HTTP 请求并返回响应
DNS 解析器可能命中缓存,也可能代表客户端从根、顶级域和权威 DNS 查询。DNS 解析完成后,客户端拿到的是一个或多个地址;它还要经过连接建立、TLS 握手、HTTP 请求、源站排队和业务处理,才能得到最终内容。
使用 CDN 的典型过程
原文用了一张流程图说明 CDN 访问过程:

一个更准确的简化过程如下:
- 浏览器、操作系统和 stub resolver 先检查本地缓存、hosts 文件和连接复用状态;
- 如果没有可用 DNS 结果,客户端向配置的递归 DNS 解析器查询业务域名;
- 业务域名可能通过 CNAME 指向 CDN 服务商提供的接入域名。递归解析器会继续解析 CNAME 链,直到得到 A/AAAA 地址或其他可用接入信息;
- CDN 的权威 DNS、Anycast 或全局流量调度系统根据解析器位置、网络线路、节点健康、容量和策略返回合适的接入地址;
- 客户端使用原始域名建立 HTTPS 上的 HTTP/2 或 HTTP/3 连接。TLS SNI 和 HTTP Host(HTTP/2、HTTP/3 中对应
:authority)仍然是用户访问的业务域名,而不是简单替换成 CDN 的 CNAME 文本; - CDN 边缘节点根据 Host、路径、查询参数、请求头和缓存配置进行路由与缓存查找;
- 如果缓存命中且仍然新鲜,边缘节点直接返回响应;如果未命中、已过期、被绕过或需要验证,则按配置回源;
- CDN 将源站响应返回客户端,并可能保存一份边缘缓存供后续请求使用。
原文中有几个需要修正的地方:
- 客户端并不一定直接拿到“全局负载均衡系统的 IP”。DNS 可能返回 CDN Anycast 地址、边缘入口地址或另一层 CNAME;具体由服务商架构决定;
- DNS 查询中通常只有域名和记录类型,不包含完整 URL 的路径。像
/images/logo.png这样的路径要到 HTTP 请求到达 CDN 后,才由边缘层处理; - 因此,DNS 调度通常不能直接根据完整 URL 路径选择缓存服务器,路径和缓存键一般由 CDN 的 HTTP 层处理;
- “距离最近”只是便于理解的说法,实际还会综合递归解析器位置、运营商、网络拓扑、节点健康和容量;
- CNAME 是 DNS 名称的别名,不是 HTTP 重定向,也不是把文件从一个地址复制到另一个地址。
CNAME 的解析过程
假设用户访问:
https://static.example.com/app.js
DNS 可能配置为:
static.example.com CNAME customer.cdn.example.net
递归解析器会继续查询 customer.cdn.example.net 的 A/AAAA 或其他记录,然后把结果返回给客户端。客户端通常不会感知“先解析了业务域名、再解析 CDN 域名”的内部细节。
传统 DNS 中,CNAME 的目标必须是域名而不是直接写 IP,且同一个名称通常不能同时拥有 CNAME 和其他类型的记录。根域名是否支持 CNAME,要看 DNS 服务商提供的 ALIAS、ANAME 或 CNAME flattening 能力,不能把子域名的配置方式直接套到根域名。
CDN 像“家门口的小卖部”,但不只是仓库
原文用康师傅泡面作比喻:如果家门口没有小卖部,就要去工厂拿;有了小卖部,就可以去附近且有货的小卖部拿。这个比喻可以帮助理解边缘缓存:
- 源站像生产和存放内容的工厂;
- CDN 边缘节点像分布在不同区域的仓库或小卖部;
- 缓存命中时,用户直接从边缘获取;
- 缓存未命中时,边缘需要向源站或上一级缓存取货;
- “附近”不只由地理距离决定,还取决于网络路径、负载和服务策略。
CDN 也不一定把资源预先复制到每个节点。许多 CDN 采用按请求回填的 pull/cache-fill 模式,热门资源才会逐渐在相应节点形成缓存。
二、回源是什么意思?
**回源(origin fetch/back-to-origin)**是指 CDN 边缘节点无法直接用本地缓存满足请求,于是向源站或上一级缓存发起请求,获取资源或验证已有资源的过程。
一个典型的多级缓存结构可能是:
客户端
-> 边缘节点
-> 区域缓存/父节点/Origin Shield(如果产品启用)
-> 源站
并不是所有 CDN 都有独立的区域缓存或 Origin Shield。有的边缘节点会直接访问源站,有的会先访问服务商配置的父缓存,以减少多个边缘节点同时回源造成的压力。
哪些情况会回源?
常见原因包括:
- 边缘节点从未缓存过该资源,发生 cache MISS;
- 资源已超过新鲜时间(TTL),需要重新获取或验证;
- 资源被主动 purge/invalidate;
- 请求命中缓存绕过规则,例如带登录态、Cookie、Authorization 或特定请求头;
- 源站响应了
Cache-Control: no-store、private或其他不允许共享缓存的指令; - 缓存键发生变化,例如查询参数、Host、编码协商或自定义请求头不同;
- 当前请求方法、状态码、Range 或响应头不符合 CDN 的缓存策略;
- 节点因为容量淘汰、重启或故障没有保留原来的对象。
回源不一定意味着“缓存里完全没有文件”。已有缓存过期后,CDN 可能携带 If-None-Match 或 If-Modified-Since 向源站重新验证;源站返回 304 Not Modified 时,CDN 可以继续使用原来的响应,而不必重新传输完整内容。
回源流程示例
客户端请求 /assets/app.js
|
v
边缘节点查缓存
|
+----+----+
| |
HIT MISS/过期/绕过
| |
直接响应 请求父缓存或源站
|
+----+----+
| |
304 200 新内容
| |
使用旧对象 保存并返回
回源次数和回源流量是 CDN 监控中的重要指标。即使用户侧响应很快,MISS 比例过高也可能让源站在流量高峰时承受很大压力。
如何减少不必要的回源?
- 给带内容哈希或版本号的静态资源设置合理的长 TTL;
- 使用稳定、可预期的缓存键,避免无意义的查询参数拆分缓存;
- 给 HTML、API 和静态资源设置不同缓存策略;
- 为高频热点配置 Origin Shield、Tiered Cache 或请求合并(具体看服务商能力);
- 为响应提供
ETag或Last-Modified,支持条件请求; - 发布时优先使用新文件名,少依赖全站 purge;
- 监控 HIT、MISS、BYPASS、回源状态码、回源延迟和源站带宽。
三、除了静态资源,API 是否可以缓存?
先区分两个概念:API 缓存和动态加速不是同一件事。
- 缓存加速:边缘保存一个响应,后续请求可以直接复用;
- 动态加速:内容仍然由源站实时生成,CDN 主要优化 DNS、路由、连接复用、TCP/TLS/QUIC 链路或边缘到源站的网络路径,不一定保存响应正文。
原文观点需要修正
原文认为 API 经常更新、可能关联用户信息,而且 CDN 主要依靠 URL 判断缓存,因此 API 不适合放到 CDN。这个结论过于绝对:
- CDN 和 HTTP 缓存通常默认更容易缓存
GET、HEAD响应,但并不是“只有 URL 能决定缓存”,缓存至少需要考虑请求方法和目标 URI,还可能结合Vary指定的请求头、Cookie、Authorization、查询参数以及 CDN 自定义缓存键; POST等方法通常不会被普通 CDN 默认缓存,但 HTTP 规范并没有把“所有 API 永远不能缓存”作为规则。只有在 API 明确允许、响应可复用且缓存系统支持时,才应考虑缓存;- API 是否适合缓存,关键不是“它是不是 API”,而是响应是否可以在多个用户之间安全复用、是否允许一定时间的旧数据,以及是否能正确表达缓存策略。
适合缓存的 API
下面这些 API 在设计清楚缓存语义后可以使用 CDN:
- 公共排行榜、公开文章列表、商品目录、地区配置;
- 不包含用户身份信息的搜索建议或公共元数据;
- 更新不频繁、允许几秒到几分钟延迟的 GET 接口;
- 公开的 JSON 配置、版本信息和静态化数据。
示例:
GET /api/public/config HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=30, s-maxage=300
ETag: "config-v17"
Vary: Accept-Encoding
这里可以让浏览器缓存 30 秒,让共享 CDN 缓存 300 秒。具体 TTL 要根据数据更新频率和业务容忍度决定。
不适合直接共享缓存的 API
以下内容不能未经设计就放到共享 CDN 缓存:
- 用户订单、账户、私信、购物车和权限信息;
- 依赖 Cookie、Authorization 或用户 ID 的响应;
- 一次性验证码、支付状态和带签名的私有下载地址;
- 可能把一个用户响应返回给另一个用户的接口;
- 对实时一致性要求很高的写后读场景。
可以使用:
Cache-Control: private, no-cache
这表示响应可以存在于用户私有缓存中,但不能由共享 CDN 直接复用,使用前需要验证。对确实不应被任何缓存存储的数据,可以使用:
Cache-Control: no-store
如果一个带 Authorization 的响应确实要允许共享缓存,必须明确设置 public、s-maxage 或其他符合规范和 CDN 文档的策略,并确保缓存键包含所有影响响应的因素。默认情况下不应这样做。
API 缓存的安全检查
设计 API CDN 缓存时,至少检查:
- 请求是否为可安全复用的 GET/HEAD,是否有必要支持其他方法;
- 响应是否包含用户身份、Cookie、Authorization 或敏感头;
- 缓存键是否包含真正影响响应的 Host、路径、查询参数和协商请求头;
Vary、Cache-Control、Set-Cookie是否与 CDN 的实际规则一致;- 是否需要忽略某些查询参数,是否会导致缓存投毒或绕过鉴权;
- 是否设置了合理的 TTL、主动刷新、版本号和故障时过期策略;
- 是否用不同用户、不同权限和不同地区进行回归测试。
不要仅仅看到响应状态码是 200 就让 CDN 缓存。缓存安全事故通常不是“CDN 把文件放错机器”,而是缓存键、鉴权和响应策略设计错误。
四、资源的新鲜度如何判定?CDN 如何更新数据?
资源何时算新鲜或过期?
资源是否新鲜,首先由源站响应头和 CDN 的缓存配置共同决定。常见响应头包括:
Cache-Control: public, max-age=300, s-maxage=3600
ETag: "asset-v42"
Last-Modified: Wed, 01 Jan 2025 00:00:00 GMT
常见规则:
max-age通常控制一般缓存的 freshness lifetime;s-maxage专门控制共享缓存(如 CDN),通常优先于max-age;Expires是较早的绝对时间写法,现代配置更常使用Cache-Control;ETag和Last-Modified用于过期后的条件验证;no-cache不是禁止存储,而是复用前必须验证;no-store表示不应存储响应;private表示响应不应被共享缓存复用;stale-while-revalidate和stale-if-error可以在 CDN 支持时控制短暂提供旧内容和源站故障时的容错。
CDN 控制台的 Edge TTL、源站响应头、Surrogate-Control 或 CDN-Cache-Control 可能共同决定边缘缓存时间。不同厂商的优先级和默认行为不完全相同,不能只看某一条响应头就断言最终 TTL。
被动更新:Pull / Cache Fill
Pull 是最常见的 CDN 缓存方式:
- 客户端请求边缘节点;
- 边缘节点发现 MISS 或需要重新验证;
- CDN 向源站或上一级缓存请求内容;
- CDN 把响应返回客户端,并按策略保存缓存;
- 后续请求在 TTL 有效期内可以直接命中。
Pull 并不代表“源站主动把文件推送到每个节点”,而是第一次访问或刷新后由 CDN 按需拉取。多个用户同时请求冷资源时,部分 CDN 会使用请求合并,避免同一时刻大量重复回源。
主动更新:Purge、预热和厂商 Push
原文把主动更新称为 PUSH,但这个词在不同 CDN 中含义不统一,不能和 HTTP/2 Server Push 混为一谈。常见的主动操作有:
- Purge/Invalidate:删除或标记 CDN 上的旧对象,使后续请求重新获取;
- 预热/Prefetch/Preload:在大促或发布前主动让指定 URL 在部分或多个节点形成缓存;
- 上传式 Push:某些厂商允许通过 API 或控制台把文件送入 CDN/对象存储;
- 版本化发布:直接使用新文件名,让新旧资源通过不同 URL 并存。
主动 purge 不等于把新内容同步到每一个节点。不同服务商的 purge 生效范围、速度、限额和按 URL/目录/标签清理能力不同;清理后再次请求通常还会经历一次 MISS 和回源。
静态资源最稳妥的发布方式通常是“内容哈希文件名 + 长缓存”,例如:
app.8f3c1.js
app.1a92d.css
更新内容时生成新 URL,而不是覆盖原来的 URL。HTML 入口可以使用较短 TTL,并在发布流程中按需刷新;否则旧 HTML 可能继续引用旧资源。
304 Not Modified 和重新验证
过期对象不一定要完整下载。CDN 可以携带验证头回源:
If-None-Match: "asset-v42"
If-Modified-Since: Wed, 01 Jan 2025 00:00:00 GMT
如果源站确认内容没有变化,可以返回:
304 Not Modified
CDN 随后继续使用已有对象,并更新它的新鲜状态。若内容已经变化,源站返回新的 200 响应,CDN 再按规则替换缓存。
如何观察 CDN 是否真的更新?
可以查看响应头和服务商日志:
# 只查看响应头;HEAD 行为可能和 GET 有差异
curl -I https://static.example.com/app.8f3c1.js
# 用 GET 获取响应头但丢弃响应体,更接近真实访问
curl -sS -D - -o /dev/null https://static.example.com/app.8f3c1.js
常见的状态有 HIT、MISS、BYPASS、EXPIRED、REVALIDATED、UPDATING 等,但具体名称由 CDN 厂商决定。例如 Cloudflare 使用 CF-Cache-Status,其他 CDN 可能使用 X-Cache 或自定义字段。不能跨服务商直接比较这些状态名的含义。
五、几个专业术语
边缘节点(Edge Node)
指接近用户网络接入位置、负责接收请求并提供缓存或代理服务的节点。它不一定是物理距离最近的机器,而是 CDN 根据网络、调度和健康策略选出的合适入口。
源站(Origin)
真正提供内容、动态计算结果或对象数据的服务器、对象存储、负载均衡器或应用平台。CDN 只是在源站前面提供缓存和代理能力,不能替代源站的业务逻辑。
回源 Host(Origin Host)
CDN 连接源站时发送的 HTTP Host 请求头。它可能与用户访问的 Host 不同,用于选择源站上的虚拟主机:
连接地址:203.0.113.10
回源 Host:www.origin.example.com
如果回源使用 HTTPS,还要检查回源 SNI、证书校验名和端口。只修改 Host 不一定能解决 TLS 证书不匹配。
回源协议(Origin Protocol)
指 CDN 到源站之间使用的 HTTP/HTTPS 和版本。客户端使用 HTTP/3,并不意味着 CDN 到源站也必须使用 HTTP/3;CDN 可以在边缘终止 HTTP/3,再用 HTTP/2 或 HTTP/1.1 回源。
对于登录、Cookie、管理接口和个人数据,通常应使用 HTTPS 回源,并验证源站证书,避免 CDN 到源站之间降级为明文 HTTP。
缓存键(Cache Key)
CDN 用来区分缓存对象的输入集合。至少通常包含请求方法和目标 URI,实际还可能包含:
- Host 和路径;
- 查询参数;
Vary指定的请求头;Accept-Encoding、语言、设备类型等协商信息;- Cookie、Authorization 或 CDN 自定义字段;
- 服务商配置的忽略、排序或归一化规则。
缓存键过于细会导致命中率低,过于粗会把不同用户或不同版本的响应混在一起。调整查询参数规则前一定要做安全测试。
TTL、Freshness 和 Stale
- TTL/Freshness lifetime:对象可以无需验证而复用的时间;
- Fresh:在新鲜时间内,可以直接使用;
- Stale/Expired:超过新鲜时间,通常需要回源验证或重新获取;
- stale-while-revalidate:部分 CDN 可以在后台验证期间短暂提供旧内容;
- stale-if-error:源站失败时,部分 CDN 可以在允许窗口内提供旧内容。
对象过期不一定马上从磁盘删除,它也可能保留并等待重新验证,或者因空间不足提前淘汰。
GSLB、Anycast 和分层缓存
- GSLB(Global Server Load Balancing):全局流量调度的泛称,可能由 DNS、Anycast、HTTP 调度或多种机制组合完成;
- Anycast:多个网络位置宣告相同地址前缀,由路由系统选择网络层面的入口;
- Tiered Cache/Origin Shield:在边缘节点和源站之间增加父缓存或保护层,减少大量边缘节点同时回源。
这些词描述的是架构能力,不代表所有 CDN 都有同名、同位置的独立设备。
动态加速
动态加速通常不以缓存响应为主要手段,而是通过 DNS 调度、网络路径优化、连接复用、TCP/TLS/QUIC 优化、请求合并或边缘接入减少动态请求到源站的延迟。
动态加速不能把数据库查询结果自动变成静态缓存,也不等于 API 一定可以共享缓存。业务仍然需要正确处理鉴权、连接、超时和源站性能。
六、排查 CDN 和回源问题
DNS 层
dig static.example.com CNAME
dig static.example.com A
dig static.example.com AAAA
检查 CNAME 是否指向预期、A/AAAA 是否存在、TTL 是否符合预期。不同递归解析器、不同运营商和不同地区可能得到不同结果,不要只用一台机器判断全球情况。
HTTP 和缓存层
curl -sS -D - -o /dev/null https://static.example.com/assets/app.js
curl -v https://static.example.com/assets/app.js -o /dev/null
重点观察:
Cache-Control、Age、ETag、Last-Modified;- CDN 的 HIT/MISS/BYPASS 状态;
Via、X-Cache、CF-Cache-Status等厂商头;Set-Cookie、Vary、Authorization是否导致绕过;- HTTPS 证书、SNI、Host 和重定向是否正确。
源站和回源层
- 直接请求源站是否返回正确 Host 对应的站点;
- CDN 到源站的端口、协议和证书是否匹配;
- 源站是否限制了 CDN 出口 IP;
- 回源状态码、连接超时和 TLS 握手是否异常;
- 是否由于缓存键或查询参数导致命中率过低;
- purge 后的首次请求是否正常回源并生成新缓存。
总结
CDN 的核心不是“把资源简单放到离用户最近的服务器”,而是由 DNS/Anycast 调度、边缘代理、缓存键、HTTP 缓存语义、回源链路和源站保护共同组成的分布式系统。
理解本文可以抓住这些重点:
- DNS 负责把业务域名引入 CDN 接入链路,URL 路径要到 HTTP 层才会参与缓存和路由;
- CNAME 是 DNS 别名,不是 HTTP 重定向;
- 回源可能发生在 MISS、过期、验证、绕过规则、主动刷新和节点淘汰等场景;
- API 不一定不能缓存,公开、可复用且允许短暂旧数据的 API 可以按规范缓存;
- 用户私有响应必须防止被共享 CDN 复用,
private、no-store、Authorization 和 Cookie 需要谨慎配置; - TTL、
Cache-Control、ETag、Last-Modified和 purge/版本化发布共同决定资源更新方式; - Pull 是按需回填,Push、预热和 purge 是不同的厂商能力,不能混为一谈;
- CDN、DNS、TLS、缓存、回源和源站需要分层监控,出现问题时应先确定故障所在层。