什么是浏览器缓存
简述: 首先我们需要知道web应用的流程:
- 用户发起http请求
- 服务器接收到请求并进行一系列处理后查询数据库
- 数据库将查询到的数据返回给服务器
- 服务器将数据进行处理,返回给web页面 那么整个应用中比较耗时的两个方面就是:发起请求(http请求、向数据库发起查询请求等)和涉及到大量计算的时候。 因此缓存不知可以发生在前端或后端,还包括数据库,可分为以下几种:
- 数据库缓存
- CDN缓存
- 代理服务器缓存
- 浏览器缓存
- 应用层缓存 选择合适的缓存,可以提高应用的性能。在这里,我们针对浏览器缓存进行一个学习总结。 浏览器缓存过程如下:
基本原理就是:每次请求前都先去缓存里读数据,没有则会向服务器要数据。
按缓存位置进行分类
Service Worker
该缓存是运行在浏览器背后的独立线程。 使用该缓存必须使用https传输协议。因为Service Worker涉及到请求拦截,所以使用https保障安全。
Service Worker 的缓存与浏览器内置的缓存机制不同,它可以让我们自由控制缓存哪些文件,如何匹配缓存、ruheduquhuancun,并且缓存是持续的。 Service Worker实现缓存功能:1. 先注册Service Worker,2. 监听到install事件以后就可以缓存需要的文件,那么在下次访问时,就可以通过拦截请求的方式查询是否存在缓存,存在缓存就直接读取缓存,否则就去服务端请求数据。
当Service Worker没有命中缓存的时候,就需要fetch获取服务端的数据。
不管我们使用Memory Cache中还是网络请求中的数据,浏览器都会显示我们是从Service Worker中获取的内容。
Memory Cache
Memory Cache也就是内存缓存,主要是页面中已经获取到的资源,如脚本、样式和图片等。 读取内存中的数据肯定会比磁盘快,内存缓存虽然高效,可是缓存持续性很短。会随着进程的释放而释放。一旦关闭标签页,内存中的缓存也就被释放了。 内存缓存很快,但是再快也不能将数据都放在内存缓存,除了持续性很短以外,内存空间比磁盘空间的容量小得多,操作系统中的内存要精打细算的使用 ,所以web应用能使用的也就不多啦。
我们可以在浏览器打开一个页面,然后观察network的size列。很多数据都是来自于内存缓存的。
Memory Cache机制保证了一个页面中如果有两个请求的,如两个相同的src或href,实际上都是只请求一个的,避免浪费。
Disk Cache
Disk Cache也就是磁盘存储,存储在硬盘中。读取速度慢,但是容量和时效性科比Memory Cache大和久。
在所有缓存中,磁盘缓存覆盖面是最大的。它会根据Http header中的字段判断:哪些资源过期了需要重新获取,哪些可以直接使用,哪些需要缓存。
即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据,且绝大多数缓存都是来自磁盘缓存。
凡是之就行存储,都会面临容量增长的问题(如微信),磁盘缓存也不例外。
在浏览器自动清理时,会有特殊的算法把‘最老的’或者‘最有可能过时的’资源删除,因此时一个一个删除的。不过每个浏览器识别该清理文件的算法不尽相同。,这也是浏览器差异的体现。
Push Cache
Push Cache,即‘推送缓存’。是http2.0新增内容。 当以上三种缓存没有被命中时才会使用推送缓存。它只在一个session中存在。一旦会话结束,就被释放。大概五分钟左右。 在国内能查到的资料很少,暂时就放弃了。
按缓存类型分类
按协商类型可以分为强制缓存和协商缓存。这两种缓存都属于磁盘缓存(或http cache)。
强制缓存
强制缓存的含义是,当客户端请求后,先访问缓存数据库看是否存在,如果存在则直接返回,不存在则请求服务器端的数据。响应后在写入缓存。
强制缓存直接减少请求次数,是提升最大的缓存策略。如果考虑通过缓存提升网页性能的话,强制缓存应该是首先被考虑的。 可以造成强制缓存的字段是以下两个。
- Expires 这是http1.0的字段,表示缓存到期时间,是一个绝对的时间(当前时间+缓存时间)
Expires:Thu, 10 Nov 2022 09:12:11 GMT
在响应消息头中设置这个字段,就可以告诉浏览器,在未过期前不用重复请求。
缺点:
- 由于是绝对时间,如果用户将客户端本地的时间进行修改,而导致浏览器判断缓存失败,重新请求该资源。除此之外时差或误差等因素,也会造成客户端和服务端的时间不一致,致使缓存失效。
- 写法复杂,错一个字母,少一个空格,都会导致变为非法属性从而设置失效。
- Cache-control 因为强制缓存的缺点,所以在http1.1中增加了Cache-control,该字段表示资源换成的最大有效时间,在该时间内,客户端不需要再向服务端请求数据。 这个是相对时间
Cache-control:max-age=259200
- max-age:最大有效时间。
- must-revalidate:如果超过了最大有效时间,必须向服务器发送请求,验证是否还有效。
- no-cache:字面上是不缓存,实际上还是要求客户端缓存内容的,只是是否使用这个内容,由后续的协商来决定。
- no-store:真正意义上的不要缓存。所有内容都不缓存,包括强制和协商。
- public:所有内容都可以被缓存。包括客户端和代理服务器,如cdn。
- private:所有内容只有客户端才可以缓存,代理服务器不能缓存。默认值。 可以混合使用:
Cache-control:public,max-age=2592000
优先级:no-store>.....max-age---去查一下吧。
从http1.0开始,Expires逐渐被Cache-control取代。 但是为了兼容http1.0和人http2.0,会同时设置,且Cache-control的优先级大于Expires的。
协商缓存
当强制缓存失效,就需要使用协商缓存,有服务器绝对缓存内容是否失效。 流程上说,浏览器先请求缓存数据库,返回一个缓存标识。之后浏览器那这个标识和服务器通讯,如果缓存未失效,则返回http状态码304表示继续使用,于是客户端继续使用缓存。如果失效,则返回http状态码200,新的数据和缓存规则(时间等信息),浏览器响应数据后,再把规则写入到缓存数据库。
协商缓存在请求次数上和没缓存是一样的,不同的是返回304,就只有状态码,没有实际数据,响应体体积上的节省是它的优点。
- Last-Modified & If-Modified-Since
- 服务器通过Last-Modified字段告知客户端,资源最后一次被修改的时间。
- 浏览器将这个值和内容一起记录在缓存数据库中。
- 下一次请求时,浏览器从缓存中找到‘不确定是否过期’的缓存。在请求头中,将上次的Last-Modified的值写入到请求头的If-Modified-Since字段。
- 服务器会将If-Modified的值,与Last-Modified的值对比。相等就是未修改,响应304 ;不相等就是200,返回新数据。
缺点:
- 因为这个时间的最小单位是秒,所以秒以下的修改是不能使用的。
- 如果文件时通过服务器动态生成的,那么该方法的更新时间永远是生成时间,尽管文件可能没有变化。所以起不到缓存作用。
因此在http1.1出现了Etag和If-None-Match
- Etag & If-None-Match Etag存储的是一个hash值,是文件的特殊标识,服务器端存储文件的Etag。 浏览器在下一次加载资源向服务器发送请求书,会将上一次返回的Etag放到请求头的If-None-Matc里,服务器只需要比较客户端传来的If-None-Match跟自己存的Etag是否一致。一致返回304,不一致返回200。
流程都是一样的。区别:
- Etag(hash)比Last-Modified(秒)更精确。
- Etag性能低于Last-Modified。因为一个是时间,另一个需要算法计算出一个hash值。
- Etag优先级高于Last-Modified。
缓存的读取规则
当浏览器请求资源时:
- Service Worker
- Memory Cache
- Disk Cache
- 强制缓存未失效,用强制缓存,不请求服务器,状态码全部都是200.
- 强制缓存失效,使用协商缓存,比较后确定是304还是200.
- 发送网络请求,等待响应。
- 将相应内容存入Disk(如果请求头信息有响应配置)
- 将相应内容存入Memory(无视http头信息配置)
- 存入Service Worker的Cache Storage(如果设置了)
浏览器行为
用户对浏览器的不同操作,会触发不同的缓存策略。
- 打开网页,地址栏输入URL,查找Disk是否有匹配,有就使用,没有就请求。
- 普通刷新(F5),tab没有关闭,因此Memory缓存是可用的,会被优先使用,然后才是Disk缓存。
- 强制刷新(Ctrl+F5),浏览器不用缓存,因此请求头部带有(Cache-control:no-cache,Pragma:no-cache(为了兼容))。服务器直接返回200和最新内容。
实操案例
注意:服务端资源改变了,客户端是不会实时更新的。
const http = require('http');
const path = require('path');
const fs = require('fs');
var hashStr = 'a hash string';
var hash = require('crypto').createHash('sha1')
.update(hashStr).digest('base64');
http.createServer(function(req,res){
const url = req.url; // 请求的路径
let fullpath; // 拼接完整路径
if(req.headers['if-none-match'] == hash){
res.writeHead(304);
res.end();
return;
}
if(url === '/'){
// 请求的是主页
fullpath = path.join(__dirname,'static/html') + '/index.html';
}else{
fullpath = path.join(__dirname,"ststic",url);
res.writeHead(200,{
'Cache-Control':'max-age=10',
'Etag':hash
})
}
// 根据完成路径,使用fs读取后将内容返回
fs.readFile(fullpath,function(err,data){
if(err){
res.end(err.message);
}else{
res.end(data);
}
});
}).listen(3000,function(){
console.log("服务器已启动,监听3000端口");
// http://localhost:3000
})
缓存的最佳实践
频繁变化的数据
首先使用Cache-Control:no-cache,使浏览器每次都请求浏览器,再配合Etag或Last-Modifieda验证是否有效。 虽然没有减少请求,但是减少了响应数据体积。
不常变化的数据
首先使用Cache-Control:max-age=31536000 先配置一个很大的max-age(一年)。这样每次请求都会命中强缓存。 为了解决更新问题,就在文件名或路径添加hash,版本号等动态字符。之后,通过改变动态字符达到改变引用URL的目的。