缓存
缓存具有优先级,根据其位置不同可分为下面。浏览器查找缓存的时候,依次根据优先级查找,如果都没有找到的时候,便会发起网络请求:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- network request
Service Worker
navigation.serviceWorker
Service Worker 是运行在浏览器的另一个独立线程
- 传输协议必须是HTTPS(Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。)
步骤:
- 注册Server Worker
1 | navigator.serviceWorker.register('worker.js').then(e => console.log('success')).catch(e => console.log('error')) |
- 监听
install
事件,缓存需要的文件
1 | // worker.js |
- 在下次用户访问的时候就可以通过拦截请求的方式查询是否有缓存,如果存在缓存就使用缓存,如果没有就去请求数据
- 当Service Worker没有命中缓存的时候,会根据缓存优先度去查找数据。(当时无论是从Memory Disk中还是从网络中获取数据,浏览器都是显示是从Service Worker中获取的)
Memory Cache
Memory Cache 就是内存中的缓存,它会随着进程的释放而释放。持续时间很短,存储量少
Disk Cache
Disk Cache 是存储在硬盘中的缓存,它较于Memory Cache具有时效性长(根据http返回的header设置时间),容量大的特点。
Push Cache
Push Cache 是HTTP/2.0 的内容 ,当上面三种缓存都没命中的时候,才会触发这种缓存。
它的缓存时间很短,只在一个会话(session)中存在,一点会话结束就会被释放
HTTP/2 push is tougher than I thought
- 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好
- 可以推送 no-cache 和 no-store 的资源
- 一旦连接被关闭,Push Cache 就被释放
- 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存
- Push Cache 中的缓存只能被使用一次
- 浏览器可以拒绝接受已经存在的资源推送
- 你可以给其他域名推送资源
缓存策略
通常浏览器的缓存策略分为两种:协商缓存 和 强缓存
强缓存
强缓存代表在缓存期间不需要请求,直接返回200
- Expires:HTTP/1.0产物,表示资源在某个时间后过期,并且受制于系统时间
- Cache-Control:max-age:30 优先级高于Expires,表示该资源在30s后过期,需要再次请求。
Cache-Control常见指令:
- public 响应可以同时被客户端和代理服务器缓存
- private 相应只能被客户端缓存
- max-age 缓存存活时间
- s-max-age 覆盖max-age,作用一样,只在代理服务器生效
- no-store 不缓存任何响应
- no-cache 资源被缓存,但是立即失效,下次请求验证资源是否过期
- max-stale ns内,即使缓存过期,也使用该缓存
- max-fresh 希望在30s内获取最新响应
max-age=0, no-cache 等同 must-revalidate
协商缓存
如果缓存过期了,就需要发起请求验证资源是否过期。如果资源没有改变,服务器端返回304表示无更新,并更新缓存有效期
- Last-Modified
- ETag
Last-Modified 和 If-Modified-Since
Last-Modified 表示本地文件最后的修改日期,其中If-Modified-Since
会将Last-Modified
的值发送给服务器,询问服务器在该日期后资源是否有更新,如果有就将新的资源发送回来,如果没有就返回304
弊端:
- 如果在本地打开了文件,即使没有修改也会造成
Last-Modified
的值被修改,从而导致服务端不能命中缓存导致发送相同资源 - 因为
Last-Modified
只能以秒计算,所以如果在1s内修改了文件,服务端会认为资源还是有效的,从而不能发送正确的资源
ETag 和 If-None-Match
ETag类似于文件指纹,优先级高于Last-Modified
If-None-Match
会将当前的ETag发送给服务端,如果有变动就发送新的资源回来
ETag
语法:
1 | ETag: W/"<ETag value>" |
- W 为可选值(大小写敏感),表示使用弱验证器
表示位于双引号的字符串。没有明确指定生成ETag值的方法,通常使用内容的hash以及最后修改时间的时间戳的hash值,或简单地使用版本号。
Apache将文件索引节(inode),大小(size)和最后修改时间作为输入求值得到
在3.23版本后移除inode,只留下大小和时间戳
- ETag可以避免“空中碰撞”:
如果正在编辑文章的时候,当前wiki内容被hash,然后将ETag内容放入响应中(服务端)
将更改保存到wiki页面的时候(发布数据),POST请求可以附带含有ETag值的If-Match
来检查是否最新版本。
如果不是最新版本,意味着文档已经被编辑,抛出412提示条件判断错误。
- ETag可以缓存未更改的资源
如果用户再次访问已经过期的含有ETag资源的时候,客户端就会发送一个含有If-None-Match
的字段到服务端
服务端判断两个ETag的值是否一致,如果一致则返回304状态码,告诉客户端缓存可用
如果服务端同时设置了ETag和Last-Modified,那么这两个谁的优先度高?
一般来说,ETag的优先度会比Last-Modified优先度高
from https://www.rfc-editor.org/rfc/rfc7232.txt, RFC 7232:
A recipient MUST ignore If-Modified-Since if the request contains an
If-None-Match header field; the condition in If-None-Match is
considered to be a more accurate replacement for the condition in
If-Modified-Since, and the two are only combined for the sake of
interoperating with older intermediaries that might not implement
If-None-Match.
What if both If-Modified-Since and If-None-Match are present in HTTP headers
What takes precedence: the ETag or Last-Modified HTTP header?
既然有了 ETag,那么 Last-Modified 还有存在的必要吗?
有必要存在:
- 兼容性考虑
- ETag计算需要耗费性能,对于要求不高的资源可以使用
Last-Modified
- ETag在分布式系统中,可能不同的系统产生不同的ETag,从而导致ETag不统一
vary
这个是告知代理服务器在缓存文件的时候要区分vary
字段里面的header,他会根据vary指定的字段不同而缓存不同的文件(比如一个是使用过gzip压缩过的文件,另外一个不是通过gzip压缩过的文件)
没有缓存策略的情况
如果没有缓存策略的情况下,浏览器会采用采用一个启发式算法:去响应头中的Date
减去Last-Modifiled
的值的10%作为缓存时间
实际场景
- 通常HTML不缓存或者缓存时间很短,所以可以通过打包工具,对文件名进行处理,当文件改变的时候就会更改文件名,从而使其发起新的请求。设置其他缓存为一个很长的时间。
- 对于频繁变动的文件,可以设置
Cache-Control: no-cache
使浏览器每一次都验证资源的有效性,这样做虽然不能减少请求数量,但是可以减少数据的传输。