HTTP 缓存与 CDN 加速实践:先分类响应,再定缓存键与 TTL
API 响应接入 CDN 时,顺序应是先按公共、私有、不可缓存给响应分类,再设计缓存键与 Vary,最后才设 TTL 与失效策略。共享缓存默认不复用带 Authorization 的响应,Set-Cookie 响应也不宜被共享缓存;过期后用 ETag 协商换 304。具体 CDN 是否支持 s-maxage、purge、stale-while-revalidate 需以实际供应商文档核实。
把 API 响应放上 CDN,收益来自命中率,风险来自“不该被缓存的内容被缓存”。因此顺序不该是先调 TTL,而是:给响应分类 → 设计缓存键 → 再定 TTL 与失效。API.PLUS 侧的具体 CDN 能力(是否支持 s-maxage、主动清除、stale-while-revalidate)本次没有材料可核,需以其文档为准;下面只讲与供应商无关的判断方法,协议语义以 HTTP 缓存规范 RFC 9111 为准,此处不附原文链接。
一、先分类:哪些响应可以离开源站#
| 类型 | 典型接口 | 建议响应头 | CDN 是否存 |
|---|---|---|---|
| 公共可缓存 | 公开目录、配置、榜单、静态资源 | public, max-age=60, s-maxage=300 | 存 |
| 用户私有 | 订单、账户、个人列表 | private, no-store | 不存 |
| 协商缓存 | 变化不频繁、体积较大的配置 | max-age=0, must-revalidate + ETag | 存副本,回源校验 |
| 禁止缓存 | 支付、鉴权、写操作 | no-store | 不存 |
两条容易忽略的默认规则:请求带 Authorization 时,共享缓存默认不应复用该响应,除非响应显式声明可公共缓存;响应带 Set-Cookie 时,也不应进入共享缓存。把这两条当成硬边界,能挡掉大部分越权事故。
二、缓存键才是命中率的杠杆#
默认缓存键是 URL。以下三处会悄悄打散命中率:
- 查询参数:顺序不同、附带
utm_*或时间戳,都会被当成新对象。可在 CDN 侧规范化参数或忽略无关参数。 - Vary:
Vary: Accept-Encoding是压缩所必需;Vary: Accept-Language会按语言分桶;Vary: Authorization几乎等于放弃共享缓存,不要用。 - CORS:带
Origin的请求若把 Origin 并入缓存键,变体会随来源数量膨胀。更好的是固定回一组受控的Access-Control-Allow-Origin,而不是逐来源生成。
三、TTL 与失效:短 TTL 加主动清除通常更好运维#
max-age 约束浏览器,s-maxage 约束 CDN 等共享缓存。过期不等于删除:配合 ETag 与 If-None-Match,源站可以只回 304,省下响应体。写操作之后的一致性,优先用版本化 URL(如 /v42/config.json),其次才是 purge;后者是否存在、生效延迟多久,需核实具体 CDN。
四、常见坑#
- 把 5xx 错误也缓存了,故障被延长。错误响应显式写
no-store或极短 TTL。 - 开启了压缩却没发
Vary: Accept-Encoding,不同客户端拿到错误变体。 - 只改了源站 TTL,忘了浏览器已缓存的旧副本仍在使用。
五、响应头骨架(示意,未在真实 CDN 上执行验证)#
# 公开目录接口
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30
# 用户私有接口
Cache-Control: private, no-store
# 配置接口:协商缓存
Cache-Control: public, max-age=0, must-revalidate
ETag: "cfg-v42"
以上仅为响应头写法示意,未执行、也未在具体 CDN 上验证效果;实际行为取决于缓存层对指令的支持程度。
落地顺序#
- 按上表把现有接口逐条归类,先处理“私有却可公共缓存”的高危项。
- 观测两个指标:CDN 命中率与回源率。命中率低先查缓存键,而不是先加 TTL。
- 再调 TTL,并为可缓存接口准备清除或版本化方案。
参数只是最后一公里。分类错了,TTL 调得再精细也只会把错误缓存得更久。