API 响应的 HTTP 缓存与 CDN 加速:缓存键、失效回源与验证顺序
CDN 加速 API 的前提不是调大 TTL,而是先判定哪些响应可进入共享缓存、用什么缓存键区分、失效后如何回源。文章给出按响应头判定可缓存性的顺序、缓存键纳入参数的方式、失效与回源的降级路径,以及一条可复现的响应头验证顺序;具体 CDN 供应商的失效接口与扩展指令支持情况需按官方文档核实。
给 API 加 CDN,收益通常来自“少回源”,风险则来自“缓存了不该共享的东西”。所以顺序应该是:先用响应头把可缓存边界写清楚,再决定缓存键,最后定义失效与回源的保护措施。TTL 数值是最后一步,不是第一步。
一、先判定响应能否进入共享缓存#
可缓存性由请求方法、状态码和响应头共同决定,不由 CDN 配置单方面决定。GET、HEAD 在语义上可被缓存,其他方法默认不进入缓存;具体状态码的默认可缓存性,以及各指令的精确含义,以 IETF 的 HTTP 缓存规范(RFC 9111)官方文本为准,本文未联网核对条款细节。
| 指令 | 概括含义 | 对 API 的实际影响 |
|---|---|---|
no-store | 不得存储 | 最严格的排除,适合含敏感字段的响应 |
no-cache | 可存储,但复用前须向源站验证 | 不是“不缓存”,误读会低估回源量 |
private | 仅私有缓存可存 | 用户态响应应显式标注,避免中间层留存 |
public | 允许共享缓存存储 | 只是许可,仍需其它条件成立 |
max-age | 新鲜期 | 对共享缓存与浏览器都生效 |
s-maxage | 仅共享缓存生效,并覆盖 max-age | CDN 与浏览器的生命周期分离时的主要手段 |
must-revalidate | 过期后不得使用陈旧副本 | 余额、库存等强一致场景 |
判定顺序建议固定下来:先看是否显式禁止存储,再看是否带 Set-Cookie 或授权相关信息,最后才看新鲜期。带 Authorization 的响应默认不进入共享缓存;如果确实需要,应显式声明并确认这不会造成跨用户复用,这一条比任何 TTL 都重要。
二、缓存键决定了命中率,也决定了串号风险#
缓存键的常见默认构成是主机名、路径与查询串。问题往往出在两个方向:一是把全部查询参数纳入键,导致分页游标、时间戳等噪声参数让命中率接近零;二是为了提升命中率把鉴权类参数从键里移除,于是不同用户的响应互相覆盖。
可操作的做法是用白名单声明参与键的参数,把埋在查询串里的业务维度(如语言、版本、租户)显式保留,其余参数在边缘层剥离或规范化。同时用 Vary 声明响应随哪些请求头变化,例如按 Accept-Encoding;但不要滥加,每个 Vary 维度都会切分缓存空间。个性化响应更稳妥的选择是不进共享缓存,而不是靠键去隔离。
三、失效与回源:先定义降级路径#
- 主动失效(purge)的接口形态、粒度(按 URL、按标签、按目录)与配额,因供应商而异,需按其官方文档核实,本文不作假设。
stale-while-revalidate、stale-if-error等陈旧内容扩展指令支持度同样取决于边缘实现,启用前应确认行为。- 批量失效后会出现集中回源,源站需要限流与请求合并;阈值应通过压测确定,不要照搬他人的数字。
四、用响应头做一次可复现的验证#
下面的命令只是查看响应头的示例,本次未执行,域名与路径均为占位,请替换为你的实际环境:
# 未执行示例:连续三次请求同一 URL,观察 Age 是否递增
curl -sSI "https://api.example.com/v1/items?lang=zh" | grep -iE "cache-control|age|vary|x-cache|etag"
判断要点:Cache-Control 是否与你的契约一致;Age 在三次请求间是否增长,增长说明命中了共享缓存;Vary 是否只包含你打算切分的维度。X-Cache 一类命中标记属于供应商自定义头,名称与取值需按其文档核实,不能作为通用判据。
五、边界条件#
带用户身份的响应、实时价格与库存、A/B 实验分流、按请求头返回不同结构的内容,都不适合直接进入共享缓存。若业务要求缓存,应先把这些响应拆分到独立路径,再单独设计策略。另外,浏览器缓存与 CDN 缓存是两层,用 s-maxage 分离两者时,要确认中间层不会回退到 max-age。
本文涉及的供应商失效接口、扩展指令支持情况与回源限流阈值均待核实;在没有官方文档与自有压测记录之前,不宜把上述任何一项写成固定配置。