用户登录

技术文章

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

HTTP 缓存与 CDN 加速实践:API 响应怎么分类、怎么定头、怎么失效

一句话回答

HTTP 缓存决定响应能否复用、复用多久、由谁复用,CDN 只是共享缓存的一种部署位置,因此加速的前提是把 API 响应分成可共享、仅私有、不可缓存三类。文章说明 GET 与鉴权请求在共享缓存中的默认限制,比较 max-age、s-maxage、no-cache、no-store、private、public 的适用范围,指出缓存键由方法与 URI 加 Vary 头构成,并给出忽略无关查询参数、谨慎使用 Vary、用 ETag 做条件请求、以 TTL 到期与主动 purge 处理失效的基本做法;具体 CDN 平台的缓存键配置、purge 粒度和扩展指令支持范围需要以各自文档核实。

核心要点 TL;DR
  • CDN 只复用响应,不改变响应本身;响应能否共享缓存,取决于头字段与请求是否带凭证。
  • 共享缓存默认不存储带 Authorization 的响应,除非显式声明 public、s-maxage 或 must-r
  • 缓存键是方法加 URI 再加 Vary 指定的请求头;无关查询参数和过宽的 Vary 会造成碎片化。
  • ETag 条件请求省带宽但不省往返;TTL 与 purge 的取舍取决于业务能容忍多旧的数据。

简短回答:HTTP 缓存回答的是“这个响应能不能被复用、复用多久、由谁复用”,CDN 只是把共享缓存放到离用户更近的位置。它不会让源站算得更快,只是让一部分请求不再回到源站。所以对 API 来说,“接上 CDN 就变快”并不自动成立——先要把响应分成可共享缓存、仅私有缓存、不可缓存三类,再谈加速。

第一步:先分类,再谈头字段#

按 HTTP 缓存规范,只有 GET、HEAD 这类安全方法的响应才有常规的共享缓存价值;实践中 POST 响应即便在规范允许的窄条件下可缓存,也很少这么用。更关键的是请求是否带凭证:按规范,请求带 Authorization 头时,共享缓存默认不得存储对应响应,除非响应显式声明 public、s-maxage 或 must-revalidate。也就是说,需要鉴权的接口默认就走不到 CDN 缓存这条路上。

  • 可共享:对所有调用者返回同一份内容,且不含个人数据。例如公共配置、字典表、公开榜单。
  • 仅私有:内容随调用者变化,只允许浏览器等私有缓存保存。用 private。
  • 不可缓存:账户信息、写操作结果、一次性令牌。用 no-store。

判断标准不是“这个接口快不快”,而是“两个不同调用者拿到同一个响应,会不会出事”。会出事,就不能进共享缓存。

头字段怎么定#

指令作用范围常见用法
max-age所有缓存私有缓存的新鲜期
s-maxage仅共享缓存CDN 侧单独设定,通常比 max-age 大
no-cache所有缓存可以存,但每次使用前必须回源验证
no-store所有缓存完全不写入缓存
private共享缓存禁止 CDN 存储,浏览器仍可存
public共享缓存显式允许共享缓存,常用于带鉴权但内容一致的响应
stale-while-revalidate扩展指令过期后先返回旧值、后台刷新;支持范围需按具体缓存实现核实

一个容易混淆的点:no-cache 不是“不缓存”,而是“必须验证”。真正的“不缓存”是 no-store。把这两个写反,会造成要么该省的流量没省,要么该保护的数据被存了下来。

一致性交给 ETag,别只靠 TTL

给响应加 ETag,客户端下次带 If-None-Match,未变化时返回 304,可以省下响应体。注意它省的是带宽和序列化,不是往返次数——条件请求仍然要问一次源站或边缘节点。对“宁可多一次往返也要拿到最新值”的接口,这是比缩短 TTL 更稳的做法。

缓存键:加速效果最容易被这里吃掉#

缓存键的默认构成是请求方法加目标 URI,再加上 Vary 指定的请求头。它决定了“什么算同一个资源”,配错了要么串数据,要么命中率极低。

  • 无关查询参数要忽略:utm_source、时间戳、随机数这类参数会让同一份内容被切成无数份。可以在 CDN 侧配置忽略规则,或让客户端不要带。
  • 参数顺序要归一:?a=1&b=2 与 ?b=2&a=1 在多数实现里是不同键,需要在边缘做排序或白名单化。
  • Vary 要窄:把 User-Agent、Cookie 放进 Vary,等于给每个客户端建一份缓存。Vary: Accept-Encoding 是必要的,因为它关系到压缩格式协商。

一个示意性的响应头(未执行)

下面只是头字段写法示例,未在真实环境运行,也不代表任何具体平台的默认行为:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30
ETag: "cfg-20261031-a1"
Vary: Accept-Encoding

// 用户私有数据
HTTP/1.1 200 OK
Cache-Control: private, no-store

失效:TTL 是承诺,purge 是补救#

设置了 s-maxage=300,就等于向调用方承诺“最多旧 5 分钟”。这个数字应由业务容忍度反推,而不是由服务器性能决定。需要更快更新时,再配合主动清除(purge)。

purge 的粒度、传播延迟、是否支持按标签批量清除,各家 CDN 差异很大,也直接影响回滚方案,需以具体平台的文档核实。可以确认的通用边界是:purge 属于最终一致的补救手段,不能当作实时写入的同步机制。

几个常见边界#

  • 错误响应不要长缓存:5xx 通常不该被共享缓存长期保存,否则一次抖动会扩散成一段时间的不稳定。
  • 304 也要算进监控:只看 200 的命中率会低估缓存效果,也会漏掉验证链路的异常。
  • 鉴权信息不进 URL:把 token 放在查询串里,会让它同时进入缓存键、日志和 Referer,比缓存问题更严重。

本文讨论的是通用的 HTTP 缓存语义,不涉及 API.PLUS 或其他具体平台是否支持自定义缓存键、purge API 或扩展指令——这些能力待按各自官方文档核实。落地时的最小验证路径是:先确认响应能否共享,再确认缓存键是否收敛,最后用一次“改内容→观察旧值何时消失”的检查来验证 TTL 与 purge 是否按预期生效。

作者:沐小悠

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

更多文章 GET 示例接口

「」标签下的其他文章