stale-while-revalidate 缓存策略
Mar 15, 2023 ·
4 Min Read
stale-while-revalidate
(简称SWR) 是 HTTP 的一种缓存策略,是 HTTP 的响应头 cache-control
的一个属性值。
一种由 HTTP RFC 5861(opens in a new tab) 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。
如果你有使用过 NEXTjs 或 SWR 的话,其中都有用到
stale-while-revalidate
,比如 nextjsgetStaticProps
返回的参数revalidate
就是设置这个属性。
浏览器 SWR 缓存策略主要分为两部分:
- 判断响应缓存是否过期。
- 重新校验响应数据时效性。
📨 例子
假设有一请求的响应头有如下字段👇🏻
Cache-Control: max-age=600, stale-while-revalidate=30
- 那么浏览器收到该响应后,会将结果缓存到磁盘中,请求的结果在600s内都是新鲜(stale的反义词)的,如果在600s内发起了相同请求,则直接返回磁盘缓存。
- 若 600s~630s(
max-age
+stale-while-revalidate
)内发起了相同的请求 ,虽然缓存过期了,但依旧使用该缓存结果;另外同时进行异步 revalidate,即重新验证,相当于浏览器在背后自己发一个请求,响应结果留作下次使用。 - 在 630s 秒后,发起相同请求时,则将和第一次请求一样,从服务器获取响应结果,并且浏览器并把响应结果缓存起来。
那么使用它的优点是❓❓❓
在传统的同步更新缓存机制中,一个资源的缓存过期之后,如果想再次使用它,需要先对该缓存进行 revalidate。在 revalidate 执行期间,客户端就得等待,直到 revalidate 请求结束。
而 stale-while-revalidate
可以进行后台缓存刷新,提升加载速度。
没有十全十美的东西,自然它也不例外👇🏻
stale-while-revalidate
进行异步缓存更新,因此 revalidate 期间时返回上一次的缓存结果,这也导致会牺牲一次(通常)资源的新鲜性。
实操案例
服务端代码
const http = require('http')const app = http.createServer()let firstRequest = true
app.on('request', (req, res) => { const { url } = req const { host } = req.headers const route = new URL(url, `http://${host}`).pathname
let response = { stat: 0, data: {} }
switch (route) { case '/api/list': // 模拟请求后一次后,响应数据改变 if(firstRequest) { response = { stat: 1, data: '第一次请求的数据' } firstRequest = false } else { response = { stat: 1, data: '最新的数据' } } break default: break }
res.setHeader("Access-Control-Allow-Origin", "*") res.setHeader('Content-Type', 'application/json; charset=utf-8') res.setHeader('Cache-Control', 'max-age=30, stale-while-revalidate=10') res.end(JSON.stringify(response))})
app.listen(8888, () => { console.log('webserver running')})
客户端代码
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div>第一次请求的数据:<span id="first"></span></div> <div>30s~40s请求的数据:<span id="second"></span></div> <div>40s后请求的数据:<span id="third"></span></div>
<script> async function fetchData() { const res = await (await fetch('http://localhost:8888/api/list')).json() return res }
async function render() { const firstRes = await fetchData() document.getElementById('first').innerText = JSON.stringify(firstRes)
setTimeout(async () => { // 31秒后自动请求 const secondRes = await fetchData() document.getElementById('second').innerText = JSON.stringify(secondRes) }, 31000)
setTimeout(async () => { // 41秒后自动请求 const secondRes = await fetchData() document.getElementById('third').innerText = JSON.stringify(thirdRes) }, 41000) }
render() </script></body></html>
运行结果👇🏻
第一次请求的数据:{stat: 1,data: '第一次请求的数据'}
30s~40s请求的数据:{stat: 1,data: '第一次请求的数据'}
40s后请求的数据:{stat: 1,data: '最新的数据'}
.
Last edited Feb 15