HTTP 缓存与 CDN 加速实践:缓存键、校验与回源责任怎么划(2026-11-01)
CDN 加速效果主要由三件事决定:缓存键是否只包含决定响应内容的因素、新鲜度与校验策略是否匹配内容变更节奏、源站与 CDN 的回源责任是否边界清晰。缓存键过宽会降低命中率,过窄会造成错配;共享缓存与私有缓存的有效期需要分别看待;回源阶段应由源站保证状态码与缓存头的正确性,而不是假设 CDN 一定会缓存。
多数项目里,CDN 的加速效果并不取决于“有没有接入”,而取决于三点:缓存键是否稳定、新鲜度策略是否与内容变更节奏匹配、回源责任是否只有一处定义。以下讨论的是划分方法与边界条件;具体 CDN 厂商的默认行为、指令支持范围与配额,需以其官方文档和你的实际流量核对,本文未做厂商侧核验。
缓存键:命中率和错配是同一个开关#
缓存键决定“两个请求算不算同一个资源”。它本质上回答一个问题:哪些请求输入会改变响应的字节内容。
- 键放太宽:把与内容无关的查询参数、跟踪参数、会话标识纳入键,命中率被稀释,缓存层等于白建。
- 键放太窄:把影响内容的要素排除在外,例如忽略
Accept-Encoding、语言协商或设备类型,就会出现一种编码或语言的内容被发给另一类客户端。
可操作的顺序是先列清单、再定键:把“决定响应内容”的因素逐项写出(路径、必要查询参数、内容协商维度),只有落在清单里的才进键。HTTP 的 Vary 头用于声明内容协商维度,但它同时会细分缓存,维度越多命中率越低,因此应只保留真正影响内容的项。CDN 是否对 Vary 全量支持、是否会在边缘改写键,属于厂商行为,实施前需核实。
新鲜度与校验:分清共享缓存和私有缓存#
缓存是否直接复用响应,取决于是否“新鲜”。Cache-Control 中的 max-age 面向包括浏览器在内的缓存,s-maxage 面向 CDN 这类共享缓存,两者可以给出不同的有效期;private 表示不允许共享缓存存储,no-store 表示不存储。这些是协议层面的语义,具体取值需要结合内容的变更方式决定,而不是套用固定数值。
| 场景特征 | 更适合的策略方向 | 主要风险 |
|---|---|---|
| 内容有明确发布时刻,可主动刷新 | 较长有效期 + 发布时失效 | 失效操作遗漏会造成长时间旧内容 |
| 内容随时可能变,无法预知时刻 | 较短有效期 + 条件请求校验 | 校验请求增多,回源压力上升 |
| 含用户态数据 | private 或不缓存 | 被共享缓存命中导致越权泄露 |
带校验的路径依赖源站正确实现条件请求:客户端或 CDN 带 If-None-Match,源站比对后返回 304 或新内容。304 不携带正文,但会消耗一次往返,因此“短有效期 + 校验”优化的是带宽与渲染成本,不是往返次数。
回源责任:源站管正确性,CDN 管复用#
边界可以这样切:
- 源站负责返回正确的状态码、可缓存的响应头与稳定的校验标识(如
ETag)。错误的状态码若被当作可缓存响应保存,后续请求会持续拿到错误。 - CDN 负责按策略复用响应、处理并发回源合并、执行失效操作。它不能替源站判断内容是否该公开。
常见的责任错位是源站“假设 CDN 一定会缓存”,于是把本该由响应头表达的语义交给平台配置;一旦配置改动或缓存被清除,源站就会被流量直接打到。反过来,源站对带 Set-Cookie 的响应若不声明 private,在共享缓存上是高风险组合。
一个未执行的示例#
下面的响应头仅用于说明字段位置与组合关系,未在任何环境实际执行,也不构成取值建议;实际取值需按内容变更节奏确定。
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=60, s-maxage=300
ETag: "YOUR_ETAG_VALUE"
HTTP/1.1 304 Not Modified
ETag: "YOUR_ETAG_VALUE"
其中 YOUR_ETAG_VALUE 为占位值。
上线前核对#
- 列出决定响应内容的因素,与缓存键逐项比对,确认没有多余项和缺项。
- 确认含用户态数据的响应不会被共享缓存命中。
- 确认源站在条件请求下正确返回
304,且错误响应不被长期缓存。 - 确认失效路径可操作、可观测:内容更新后,旧内容还能存活多久。
- 就所选 CDN 的默认缓存规则、对
Vary与各指令的支持范围、清除操作的生效时间,向厂商文档或支持渠道核实。
本文未涉及具体 CDN 产品的当前默认配置与版本特性,这部分信息随产品变化,应在实施前单独核实。