用户登录

技术文章

外部链接将在新标签页打开。

HTTP 缓存与 CDN 加速:先写对响应头,再谈边缘节点

一句话回答

HTTP 缓存的效果取决于响应头是否显式回答了四件事:能否存储、新鲜期多长、过期后如何验证、对哪些请求可复用。CDN 把同一套语义分布到边缘,收益主要来自缓存键设计与回源策略,而不是节点数量。含用户态数据的响应需要用 private 或 no-store 隔离,避免被共享缓存复用。

核心要点 TL;DR
  • 可缓存性、新鲜期、验证方式、可复用对象,是四个必须显式回答的问题,缺一项就会由浏览器或 CDN 的默认规则替你决定。
  • CDN 的加速效果更多取决于缓存键和回源策略,而不是节点数量。
  • 带用户身份或个性化内容的响应应使用 private 或 no-store,避免进入共享缓存。

缓存不是部署后自动获得的能力,而是一组由响应头表达的约定。浏览器和 CDN 都会在信息不足时替你补默认值,而这些默认值往往与业务预期不一致。本文面向 API.PLUS 的开发者读者,梳理 HTTP 缓存的判断顺序,以及把它放到 CDN 上时需要额外注意的部分。文中涉及的协议条款以规范入口为准,本文未联网核验条款细节,具体 CDN 厂商行为待核实。

缓存要先回答的四个问题#

HTTP 缓存语义由 RFC 9111 定义(链接为规范入口)。无论中间隔着多少层,一套响应头最终要回答的就是下面四件事。

一、能不能存

Cache-Control 的 public 与 private 决定谁有权存储。private 面向单用户缓存(通常是浏览器),public 允许共享缓存(CDN、代理)保存。no-store 表示不存储。需要特别区分的是 no-cache:它并不禁止存储,而是要求每次复用前先向源站验证,因此它换来的是一致性,不是带宽节省。

另一个容易被忽略的规则是:带 Authorization 请求头的响应,共享缓存默认不得存储,除非响应中显式包含 public、s-maxage 或 must-revalidate。这意味着给鉴权接口加缓存,必须是有意为之,而不是靠某个默认值蒙对。

二、存多久

max-age 给出新鲜期秒数。s-maxage 只作用于共享缓存,且优先级高于 max-age——这正是「浏览器缓存短一些、CDN 缓存长一些」的实现方式。若响应没有任何显式新鲜度信息,规范允许缓存做启发式估算,但没有规定具体比例,因此这类行为在不同实现之间不可移植,不应作为设计依赖。

三、过期之后怎么办

过期不等于失效。源站返回 ETag 或 Last-Modified 后,缓存可以发起条件请求(If-None-Match / If-Modified-Since),源站回 304 即可复用旧副本。注意这里节省的是响应体,往返本身仍然发生。

由 RFC 5861 定义的 stale-while-revalidate 与 stale-if-error 进一步放宽了边界:前者允许在后台刷新的同时先返回旧副本,后者允许源站出错时继续提供过期副本。这两个指令属于扩展,是否被特定 CDN 支持待核实,上线前需要在目标平台上确认。

四、给谁复用

Vary 声明响应随哪些请求头变化。它会被并入缓存键,因此 Vary: * 或把 User-Agent、Accept-Language 全量列入,会让缓存条目按组合爆炸式增长,命中率随之下降。Vary 应当只列真正影响响应内容的头。

CDN 改变的其实只有两件事#

CDN 在语义上不创造新规则,它做的是把上述判断分布到离用户更近的节点,并引入「边缘命中 / 回源」这条额外路径。由此产生的实际差异集中在两点:

  • 缓存键的构成。多数实现默认把主机名、路径与查询串纳入键,并允许追加指定请求头或 Cookie。把与内容无关的参数(时间戳、跟踪参数)留在键里,等于主动制造未命中。
  • 共享缓存的身份边界。边缘节点服务多个用户,任何被标为 public 的个性化响应都会被下一个人拿到。这条风险与缓存配置无关,只与响应头是否诚实有关。

下例仅示意两种响应头的语义差别,未执行,也不代表任何真实服务的返回值:

# 静态资源:长新鲜期 + 内容指纹路径
Cache-Control: public, max-age=31536000, immutable
ETag: "a1b2c3"

# 用户态接口:不进共享缓存,允许浏览器短存并验证
Cache-Control: private, max-age=0, must-revalidate
Vary: Accept-Encoding
ETag: "d4e5f6"

可以按这个顺序落地#

  1. 给每类响应标注归类:静态、可匿名共享、含用户态、绝不缓存。归类先于配置。
  2. 含用户态的响应统一走 private 或 no-store,并在边缘侧确认不会被改写为可共享。
  3. 为可共享响应补上 ETag,让过期后的请求有机会以 304 收尾。
  4. 收窄 CDN 缓存键:剥离无关查询参数,Vary 只保留必要项。
  5. 上线后按「边缘命中率 / 回源比例 / 304 占比」三项观察。这些指标的具体口径与采集方式取决于所用 CDN,待核实,本文不给出参考数值。

边界说明#

本文只覆盖协议层面的缓存语义与由此推导的实践顺序,不涉及具体 CDN 厂商的配置项名称、缓存刷新接口、预热机制与计费规则,这些均需以目标平台的当前文档为准。若你的服务存在按租户定制的内容或灰度分流,缓存键与 Vary 的设计需要单独评估,不能直接套用上文的静态资源方案。

常见问题(FAQ)

no-cache 是不是等于完全不缓存?
不是。按 HTTP 缓存语义,no-cache 允许存储响应,但每次复用前必须先向源站验证。真正要求不落盘的是 no-store。
CDN 命中率偏低,排查顺序是什么?
先看缓存键是否被 Cookie、随机 query 参数或过宽的 Vary 放大,再看源站返回的是否为 no-store、private 或过短的 max-age,最后才看边缘节点的分布与回源链路。

作者:沐小悠

本文约 5 分钟读完 · 关键词:HTTP 缓存、CDN 加速、Cache-Control、ETag、Vary

更多文章 GET 示例接口

「」标签下的其他文章