当CDN边缘节点的缓存刚刚过期,同时有大量用户请求到达时,传统缓存策略会面临两难:如果立即回源获取新内容,所有请求都要等待源站响应,延迟可能从几毫秒飙升到几百毫秒;如果继续提供旧缓存,又无法保证内容时效。Stale-while-revalidate(SWR)就是用来解决这个矛盾的HTTP缓存扩展,它允许CDN在缓存过期后先返回旧内容,并同时触发一次后台更新。

Stale-while-revalidate 解决了什么问题
在标准HTTP缓存模型中,Cache-Control: max-age=60表示资源在60秒内是新鲜的,CDN可以直接从缓存返回,不需要询问源站。一旦超过60秒,资源变为陈旧,此时如果坚持新鲜度优先,第一个到达的请求会同步回源获取新版本,后续请求继续等待或排队。源站响应慢、连接超时或者并发量高时,这个回源过程会显著放大用户感知延迟。
SWR在Cache-Control中引入一个额外的窗口参数,例如max-age=60, stale-while-revalidate=1800。它告诉CDN:在60秒新鲜期内直接返回缓存;在第61秒到第1860秒之间,资源虽然已经过期,但仍然可以先返回旧副本,同时在后台异步请求源站。更新成功后,缓存被替换为新版本;如果更新失败,旧副本仍然可服务,直到SWR窗口结束。这样用户请求永远不会因为缓存过期而被阻塞在回源链路上。
RFC 5861定义了stale-while-revalidate和stale-if-error两个指令。前者负责后台刷新,后者在源站故障时允许使用陈旧缓存兜底。两者配合可以显著降低CDN回源压力和用户等待时间,尤其适合读多写少、内容实时性要求不极端的资源。
Cache-Control参数与CDN行为详解
配置SWR只需要在源站响应头中添加两个值,一个是新鲜期max-age,另一个是后台更新窗口stale-while-revalidate。假设设置为max-age=300, stale-while-revalidate=600,那么前5分钟CDN直接返回缓存,不访问源站;从第5分钟到第15分钟,第一个请求会触发异步回源,但用户拿到的仍然是旧缓存;更新完成后,后续请求自动获得新内容。
需要注意,CDN在SWR窗口内的行为并非所有请求都触发回源。大多数实现会合并并发更新请求:如果已经有回源任务在进行,其他请求继续使用旧缓存,不会重复回源。这样可以把源站请求量控制在很低的水平。不同CDN对该指令的支持程度不同,Cloudflare、Fastly、Akamai等主流服务均支持SWR;AWS CloudFront对源站返回的SWR头默认不处理,需要通过TTL策略或Lambda@Edge自行实现类似逻辑。
此外,stale-while-revalidate与stale-if-error的区别值得厘清:前者是在正常过期后立即返回旧内容并后台更新,后者只在源站返回5xx或网络错误时才提供过期缓存。两者可以同时出现在Cache-Control中,例如max-age=60, stale-while-revalidate=3600, stale-if-error=86400,分别覆盖常规过期和故障兜底两种场景。
Nginx与Cloudflare配置示例
如果使用Nginx作为源站或反向代理,可以通过add_header指令输出SWR响应头。例如:
location /api/ {
proxy_pass http://backend;
add_header Cache-Control "max-age=60, stale-while-revalidate=3600";
}
上述配置对/api/路径下的响应添加缓存控制头,新鲜期为60秒,SWR窗口为1小时。Nginx本身不实现后台更新逻辑,它只是把响应头传递给CDN或浏览器;真正执行异步刷新的是前方的CDN节点。
Cloudflare默认尊重源站返回的SWR头。如果源站返回Cache-Control: max-age=60, stale-while-revalidate=3600,Cloudflare边缘节点会按该策略处理。通过Cloudflare Workers也可以手动控制缓存行为:
async function handleRequest(request) {
const response = await fetch(request);
const newHeaders = new Headers(response.headers);
newHeaders.set('Cache-Control', 'max-age=60, stale-while-revalidate=3600');
return new Response(response.body, {
status: response.status,
headers: newHeaders
});
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
这段Worker代码拦截请求并设置响应头,把SWR策略应用到所有经过的响应。实际项目中通常只针对特定路径或文件类型开启SWR,避免影响需要实时数据接口。
适用场景与实施注意事项
SWR非常适合读多写少、对最终一致性容忍度高的内容,比如文章详情页、新闻资讯、商品介绍、静态图片、CSS和JavaScript文件,以及公开的列表API。用户在这些场景下通常感受不到旧缓存与新版本的微小差异,却能获得稳定的低延迟体验。对于需要强一致性的操作,例如库存扣减、支付确认、个人中心数据,不应使用SWR,否则用户可能看到错误的余额或库存状态。
另一个风险点是SWR窗口设置过长。如果源站更新频率较高,而SWR窗口设为数小时,用户可能长时间看到过时内容。建议根据业务可接受的数据延迟来设置窗口,例如文章页可以设5到10分钟,API列表设1到5分钟。同时配合合理的max-age,让大部分流量都命中新鲜缓存,只有少量请求触发后台刷新。
还需要注意,SWR主要对CDN有意义,浏览器的原生缓存通常不会在后台异步更新;但当浏览器直接请求源站或经过支持SWR的网关时,它也会遵循响应头。对于JavaScript资源,若需要立即更新,可以通过版本号或指纹控制缓存键,而不是单纯依赖SWR。回源失败时,配合stale-if-error可以继续提供旧内容,避免故障期间用户看到错误页。
总结
Stale-while-revalidate在缓存策略中提供了一种低成本的异步更新机制,能有效降低缓存过期瞬间的回源延迟压力,同时提升整体可用性。它并非银弹,使用时需要根据业务对数据新鲜度的要求合理设置新鲜期和SWR窗口,并确认所选CDN是否支持该指令。把SWR与版本化缓存键、stale-if-error等策略组合使用,可以在用户体验和系统负载之间取得更好的平衡。
CDN缓存Stale-while-revalidate用户体验修改时间:2026-08-21 08:47:52