CDN 边缘缓存中的“救急”指令:stale-while-revalidate 与 stale-if-error 到底怎么用?

面对突发流量或源站不稳定,传统缓存过期策略会让用户硬等。stale-while-revalidate 和 stale-if-error 两个 Cache-Control 指令,能在不影响用户体验的前提下异步更新缓存或提供降级内容。本文用白话来拆解这两个指令的原理、适用场景和配置方法。

CDN 边缘缓存中的“救急”指令:stale-while-revalidate 与 stale-if-error 到底怎么用?
封面图:ZuCDN · ZuCDN 原创

一个真实的两难场景与CDN边缘缓存响应头Ca

先看关键判断

折腾CDN边缘缓存响应头Ca时,我发现最麻烦的往往不是安装,而是配置。假设你的网站首页依赖一个实时更新的接口,设置短缓存(比如 10 秒)可以保证新鲜度,但源站一扛不住,用户就反复回源打爆服务器;设置长缓存(比如 1 小时)又怕用户看到过时内容。有没有办法让 CDN 在缓存过期后先“假装”内容还是新鲜的、把旧版本发给用户,与此同时偷偷去源站拿新版本?或者当源站出故障时,允许 CDN 继续提供旧内容而不要返回 502?

这两个问题恰好对应 HTTP 标准中的两个 Cache-Control 扩展指令:stale-while-revalidatestale-if-error。虽然名字看着拗口,理解之后你会发现它们就是应对缓存过期尴尬的“救急”方案。

先复习一下缓存的基本单元:stale vs fresh

故障定位思路

在 HTTP 缓存体系里,一个资源通过 Cache-Control: max-age=600 声明它在 600 秒内是“新鲜的(fresh)”。超过 600 秒,资源变为“陈旧的(stale)”。默认情况下,CDN 节点在收到对陈旧资源的请求时会直接回源验证或重新下载,导致用户等待这一额外的网络往返。

问题在于:很多资源(比如文章页面、商品详情)即使变旧几秒钟,用户根本察觉不到差别,但硬等回源却会明显拖慢加载速度。两个指令就是为了减少这种不必要的等待而设计的。

stale-while-revalidate:先应急,再悄悄更新

含义与流程

语法:Cache-Control: max-age=60, stale-while-revalidate=30

这表示资源在 60 秒内是新鲜的。60 秒之后进入“陈旧的但允许异步验证”的窗口期,持续 30 秒。在这 30 秒内,CDN 节点会做两件事:

  • 立即把当前缓存的旧版本返回给用户(瞬间响应);
  • 同时在后台发起一个回源请求,拿回最新版本并更新本地缓存。

用户完全感觉不到延迟,而下一次请求就会从更新后的缓存中读取。如果 30 秒窗口也过了,资源彻底过期,后续请求会强制同步回源(即用户需等待)。

适合用在什么地方?

  • 高并发且容忍短暂过时的页面:如新闻首页、排行榜,几秒的延迟不影响准确性。
  • CDN 边缘节点利用:配合长时间 max-age 和短时间 stale-while-revalidate,可以大幅降低回源 QPS,避免源站被击穿。
  • API 响应:对于一些非关键性的 JSON 数据(如评论数、点赞数),允许滞后几秒远比失败或慢速好。

配置时要注意什么?

  1. stale-while-revalidate 的值不宜过大,建议是 max-age 的 1/2 到 1 倍,避免用户长时间看到过时数据。
  2. 需要 CDN 支持该标准。主流 CDN(Cloudflare、Akamai、Fastly、阿里云 CDN 等)都已支持。部分源站反向代理(如 Nginx、Varnish)也可以配置。
  3. 与 Etag/Last-Modified 配合效果更佳,异步验证时可使用条件请求(If-None-Match)进一步减少传输开销。

stale-if-error:源站挂了,也别给用户看错误

含义与流程

语法:Cache-Control: max-age=60, stale-if-error=86400

当源站返回 5xx 错误(或响应超时)时,CDN 允许在最长 86400 秒(24 小时)内提供缓存中的陈旧内容,而不是直接返回 502 或 504 给用户。注意,这个指令只在源站错误时生效;如果源站正常,即使缓存已过期,也会进行正常的同步回源。

典型场景

  • 关键页面保底:比如支付结果页、物流追踪页面,宁愿让用户看到几小时前的状态也不要看到一个“服务不可用”的白屏。
  • 突发流量导致源站过载:缓存命中率突然下降,大量回源请求压垮源站,stale-if-error 可以构建一个“弹性缓冲”,让 CDN 在源站恢复前持续供应旧版本。
  • CDN 预热不完全:新上线的资源还没全部缓存,如果源站临时挂掉,这个指令能避免大面积错误。

与 stale-while-revalidate 的区别和配合

两个指令可以同时使用:Cache-Control: max-age=300, stale-while-revalidate=150, stale-if-error=86400。这样,在 300–450 秒之间用户获得陈旧内容但后台刷新;450 秒之后,如果源站正常则同步回源,如果源站故障则继续提供陈旧内容直到 24 小时后。

实际配置示例(以 Nginx 和 CDN 为例)

通过源站响应头控制

在 Nginx 中为静态资源添加如下指令(适用于后端返回的响应头):

location /api/ {
    add_header Cache-Control "public, max-age=60, stale-while-revalidate=30, stale-if-error=604800";
}

这样 CDN 节点就会遵循源站给定的指令。注意:某些 CDN 可能要求在 CDN 控制台单独开启“异步验证”或“错误时使用陈旧缓存”的功能,即使源站发了头也不会自动生效。

直接在 CDN 控制台配置

大多数 CDN 允许你覆盖或添加 Cache-Control。以 Cloudflare 为例,在“缓存规则”中可以设置“Edge Cache TTL”和“Stale While Revalidate”开关。阿里云 CDN 在“缓存过期时间”中支持自定义响应头。具体操作请查阅对应平台的文档。

验证和调试

配置前的检查

  1. 使用 curl 查看响应头curl -I https://your.cdn.domain/api/test 确认 Cache-Control 包含需要的指令。
  2. 模拟过期请求:设置较短 max-age,在过期后查看是否立刻返回内容(stale-while-revalidate 生效时 Age 头会大于 max-age)。
  3. 模拟源站故障:临时停止后端服务,观察 CDN 节点响应码是否为 200(而非 502)且内容为缓存旧版本。
  4. 注意 HTTP 标准一致性:stale-while-revalidate 要求 CDN 必须异步验证,如果 CDN 实现不当可能导致同步回源。建议用浏览器开发者工具对比有无该指令时的加载时间。

需要小心的坑

先看关键判断

  • 绝不能用于动态 API:如果接口对实时性要求极高(如在线人数、股票价格),用 stale-while-revalidate 会造成数据冲突。
  • stale-if-error 可能隐藏故障:如果源站已经挂了很久,陈旧内容被长期缓存,运维人员可能察觉不到问题。建议配合监控系统,一旦频繁触发 stale-if-error 就告警。
  • 浏览器与 CDN 行为不同:这两个指令标准是应用于共享缓存(CDN)的,浏览器(私有缓存)通常也支持,但要注意有些老浏览器忽略这些字段。

CDN边缘缓存响应头Ca:总结

配置前的检查

stale-while-revalidate 和 stale-if-error 不是魔法,它们只是让缓存策略从“要么新鲜要么回源”的二元模式升级为“允许适度陈旧、异步更新、错误兜底”的弹性模式。对于想要提升终端用户响应速度又不想过度回源压榨源站的运维人员来说,是性价比极高的配置。下次遇到缓存过期尴尬时,不妨试试这两个指令。后续只要定期检查关键指标,CDN边缘缓存响应头Ca就不会变成维护负担。

延伸阅读