HTTP 缓存依靠新鲜度与验证复用响应
依据 HTTP 语义区分可存储、新鲜、过期和重新验证,并说明缓存键、Vary 与认证内容的风险。
HTTP 缓存依靠新鲜度与验证复用响应
HTTP 缓存保存可复用的响应,使后续等价请求不必每次都从源服务器取得完整内容。正确缓存可以减少延迟、带宽和源站负载;错误缓存则会把过期数据、个性化内容甚至其他用户的信息返回给不该接收的人。缓存行为由消息语义、缓存键、新鲜度和验证共同决定,不只是给服务器前面放一个存储层。
私有缓存与共享缓存承担不同风险
私有缓存服务于单个用户,浏览器缓存是常见例子。共享缓存可为多个用户复用响应,例如代理或 CDN。某个响应适合保存在个人浏览器中,不代表可以由共享缓存跨用户返回。
包含认证或个性化信息的响应需要明确策略。private 指令限制共享缓存存储,no-store 要求缓存不存储相应消息,但应用仍要考虑日志、页面历史和业务层副本。缓存指令不能替代访问控制。
共享缓存位于客户端与源站之间,可能有多层。浏览器、边缘节点、反向代理和应用内部缓存各自保存状态。排查“为什么仍看到旧内容”时,需要逐层确认响应头、对象年龄和失效路径。
可存储不等于可以立即复用
缓存先判断响应是否允许存储,再判断存储的响应能否用于当前请求。请求方法、状态码、Cache-Control 指令、认证头和缓存类型都会影响可存储性。常见的 GET 成功响应容易缓存,但重定向、错误响应和部分内容在满足条件时也可能被存储。
no-store 与 no-cache 含义不同。no-store 关注不存储;no-cache 允许保存,但再次使用前必须成功验证。把二者都理解为“关闭缓存”,会失去精确控制,也可能造成意外复用。
应用框架或 CDN 的默认行为不能取代协议检查。服务应对敏感和动态资源显式发送策略,并通过真实链路验证中间层是否按预期处理。
缓存键决定哪些请求被视为等价
缓存键至少由请求方法和目标 URI 构成。若同一 URI 根据语言、压缩、设备或其他请求头返回不同表示,响应可以用 Vary 指明哪些请求字段参与匹配。缺少必要的 Vary,缓存可能把一种表示发给另一类请求。
把过多高基数字段加入键会显著降低命中率,并增加存储对象数量。把 Cookie 整体作为变化依据常会让共享缓存几乎无法复用。接口设计应区分真正影响表示的维度与仅用于追踪的字段。
查询参数通常属于 URI,因此会影响键。参数顺序、默认值和无关跟踪参数可能制造语义相同但键不同的对象。规范化必须保证不会把实际不同的请求合并。
新鲜度允许在期限内直接复用
新鲜响应可以在不联系源站的情况下复用。源站可用 max-age 等指令给出新鲜寿命,共享缓存还可使用面向共享场景的策略。响应的当前年龄不仅取决于在某个节点存了多久,还要考虑上游传递的年龄信息和时间。
过期不等于对象必须删除。过期响应通常需要验证后才能再次使用;在明确允许的故障或客户端条件下,也可能提供陈旧响应。服务若依赖陈旧数据降级,应定义允许陈旧多久、向用户暴露何种状态以及哪些资源绝不能陈旧。
没有显式新鲜度时,缓存可能依据协议规则进行启发式判断。关键接口不应依赖各中间层可能不同的猜测,应明确表达策略。
条件请求让验证不必重传内容
缓存保存过期响应后,可以携带验证器向源站发起条件请求。实体标签 ETag 可标识表示版本,Last-Modified 提供修改时间。若表示未改变,源站返回 304,缓存更新元数据并继续使用已有内容,无需传输完整正文。
验证器必须对应实际表示。若同一资源经过压缩、语言协商或转换产生多个字节表示,强验证器不能错误地把不同内容声明为完全相同。弱验证器适合语义等价但字节不完全一致的场景,其可用范围受协议约束。
最后修改时间精度较低,也可能因部署复制而不可靠。它仍有实用价值,但不能在所有更新频率和分布式写入场景中替代版本标识。
失效比写入缓存更难
不安全方法成功修改资源时,相关缓存响应需要失效,但协议无法自动推断所有业务关联。例如更新文章可能同时影响文章详情、列表、标签页和 sitemap。若这些 URI 各自缓存,应用必须建立明确的依赖或采用可接受的短新鲜期。
主动清除 CDN 对象可以缩短传播,但清除操作本身可能延迟、失败或只影响部分层。版本化静态资源通过改变 URL 避免原位失效,适合内容不可变的构建产物;动态资源仍需新鲜度和验证。
缓存穿透和集中失效会让大量请求同时回源。请求合并、随机化过期时间和分层预热能降低峰值,但要防止一个失败响应被错误共享给所有等待者。
缓存不能突破授权与隐私边界
缓存键若不包含影响授权结果的条件,共享层可能把有权限用户的响应返回给无权限用户。最稳妥的起点是默认不共享敏感响应,只有在资源确实公开且表示与身份无关时再明确开放共享缓存。
错误响应也可能包含敏感细节,并且某些错误状态可被缓存。登录、权限检查和用户资料接口要测试正常与异常路径的响应头。退出登录或撤销权限后,客户端本地缓存中的旧表示也需要考虑。
缓存会保留访问痕迹和内容副本。no-store 能表达协议层要求,但系统还需要处理代理日志、监控采样和离线缓存的治理。
验证要观察完整请求链
测试缓存不能只看第二次请求是否更快。应检查 Age、ETag、Vary、Cache-Control、条件请求和状态码,分别覆盖匿名、认证、不同语言、更新前后和源站故障场景。
命中率高不一定正确。错误合并请求可以获得极高命中率,却破坏内容隔离;过长新鲜期也可能减少回源但违背数据时效。缓存指标应同时包含命中、回源、验证、陈旧响应、清除延迟和内容正确性抽检。
HTTP 缓存的可靠性来自精确表达:什么响应可以保存,哪些请求等价,多久内可直接复用,过期后如何验证,以及资源变化时怎样失效。遵守这些语义,缓存才是协议层的受控优化,而不是难以解释的旧数据来源。
资料依据
- IETF:RFC 9111, HTTP Caching,https://www.rfc-editor.org/rfc/rfc9111.html
- IETF:RFC 9110, HTTP Semantics,https://www.rfc-editor.org/rfc/rfc9110.html