缓存过期后先给旧内容再后台更新,这个缓存指令解决什么
Cache-Control 里的 stale-while-revalidate 解决的是「用户不该为缓存回源校验等待」:按 MDN 文档,它是响应指令,允许缓存在过期后的窗口期内先复用旧的响应内容,同时在后台向源站重新校验,例子里的写法是 max-age=604800, stale-while-revalidate=86400——新鲜期一周,过期后还有一天窗口可以先给旧内容再更新。它和请求指令 max-stale 不是一回事:后者表达客户端愿意接受多旧的缓存,主流浏览器并不支持携带 max-stale 的请求。
新鲜期与过期期怎么划
max-age 定义响应的新鲜期,期内缓存可以直接命中,不打扰源站。超过新鲜期即进入过期期,默认行为是「先校验再返回」:缓存拿着标识回源确认,内容没变就续用,变了就取新——这一次回源的耗时全部计进用户等待。stale-while-revalidate 在过期期里开了一个窗口:窗口内的请求立刻拿到旧内容,缓存组件在背后异步完成校验,下一个请求拿到的就是新版。代价是窗口期内用户可能看到过期内容,窗口长度就是「快」与「新」之间的取舍参数。
后台更新把延迟藏在哪一段
被藏起来的是回源校验的往返时间。对首次进入过期期的请求,普通策略下用户要等一次完整校验;带该指令时用户拿到的是本地旧副本,校验在后台进行。它特别适合内容更新节奏以小时计、访问峰值又集中在缓存刚过期的场景——比如文章列表页在早高峰集中失效时,不会出现一整批「回源慢请求」。需要立即一致性的页面(支付、库存类)不适用,旧内容窗口就是错误信息窗口。
请求侧的旧内容容忍度支持如何
max-stale 属于请求指令,方向相反:由客户端声明「我最多能接受过期 N 秒的内容」。MDN 明确注明主流浏览器不支持发送带 max-stale 的请求,所以它的实际生效场景在程序化客户端、代理与缓存中间件之间,而不是浏览器地址栏。设计缓存策略时不要把希望寄托在浏览器会带上这个声明。
和 no-cache、must-revalidate 的分工
| 指令 | 位置 | 语义 | 典型用法 |
|---|---|---|---|
| no-cache | 响应 | 可存可复用,但每次复用前必须校验 | 变化频繁但要省流量的页面 |
| must-revalidate | 响应 | 过期后必须校验才能用,源站不可达时不得继续给旧内容 | 对一致性要求高的接口 |
| stale-while-revalidate | 响应 | 过期窗口内先用旧内容,后台校验 | 列表页、静态资源 |
must-revalidate 的态度是「宁可不返回也不返回过期的」,与 stale-while-revalidate 的「先给旧的再说」正好相反,两者按页面容忍度分开使用。
页面文档与静态资源要分开设
页面文档新鲜期设短,配一个分钟级的过期窗口即可兼顾体验与新度;带指纹的静态资源新鲜期设长,再叠一个短窗口兜底极端情况。缓存只是第一层提速:AnQiCMS 基于 Go 语言运行,页面加载速度相比传统 PHP 类 CMS 有显著提升,官方口径单机可承载约 500 万 PV——运行时开销低,回源校验本身也便宜,缓存参数因此可以设得更保守一些,不必为了护源站而拉长旧内容窗口。
常见问题
这个指令会影响搜索引擎抓取吗? 搜索引擎爬虫同样可能命中旧窗口内的内容,收录更新最多滞后一个窗口期。把窗口控制在分钟到小时级,对新内容曝光的影响很小。
浏览器不支持 max-stale,那响应侧指令还有效吗? 有效。stale-while-revalidate 与 must-revalidate 都是响应指令,由中间的 CDN、反向代理与浏览器缓存实现遵循,不依赖浏览器发请求时携带声明。
旧内容窗口里内容明明更新了为什么不生效? 这是设计行为:窗口内先给旧副本,后台校验完成后下一个请求才见新版。要求即时一致就把窗口设为零,改用先校验策略。