简介
web缓存大致可以分为:数据库缓存,服务器端缓存(代理服务器缓存,CDN缓存),浏览器缓存。
浏览器缓存也包含很多内容:HTTP缓存,indexDB,cookie,localStorage等等,这里我们只讨论HTTP缓存相关内容。
在具体了解HTTP缓存之前先来明确几个术语:
缓存命中率:从缓存中得到数据的请求数与所有请求数的比率,理想状态是越高越好。
过期内容:超过设置的有效时间,被标记为“陈旧”的内容。通常过期内容不能用于回复客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否仍然准备。
验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
失效:失效就是把内容从缓存中移除,当内容发生改变时就必须移除失效的内容。
浏览器缓存主要是HTTP协议定义的缓存机制,一般应用广泛的是用HTTP头信息控制缓存。
附:浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。缓存要解决的核心问题为减少客户端对服务器的http请求,提升性能。试想,像jquery、bootstrap这种更新不频繁的资源当然没有必要每次都从服务器上获取,实际上大部分静态文件在下一次内容更新之前都没有必要再走一遍网络从服务器获取。
所以根据上面的特点,浏览器缓存有下面的优点:① 减少冗余的数据传输;② 减少服务器负担;③ 加快客户端加载网页的速度。
HTTP头信息控制缓存
根据是否需要重新向服务器发起请求来分类,将其分为两大类:强缓存,协商缓存。强缓存如果命中缓存则不需要和服务器端发生交互,而协商缓存不管是否命中都要和服务器端发生交互。强制缓存的优先级高于协商缓存。
匹配流程是:浏览器发送请求之前,根据Expire或Cache-Control判断是否命中强缓存,如果命中就从缓存获取资源;如果没有命中就发送请求,根据Last-Modified或Etag判断是否命中协商缓存,如果命中就从缓存中获取资源,否则从服务器端获取资源。
强缓存
可以理解为无需验证的缓存策略,就是说当请求一个资源时,直接从本地的浏览器缓存中读取,不发起http请求。对于强缓存来说,响应头中有两个字段Expires/Cache-Control来表明规则。
Expires:指缓存过期的时间,超过了这个时间就代表资源过期。有一个问题是由于使用具体时间,如果时间表示出错或者没有转换到正确的时区都可能造成缓存生命周期出错。并且Expires是HTTP/1.0的标准,现在更倾向于用HTTP/1.1中定义的Cache-Control。两个同时存在时也是Cache-Control的优先级更高。
Cache-Control:可以由多个字段组合而成,主要有以下几个取值:
1. max-age:指定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置:
Cache-Control:max-age=31536000
也就是说缓存有效期为(31536000 / 24 / 60 * 60)天,第一次访问这个资源的时候,服务器端也返回了Expires字段,过期时间是一年后,在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。
2. s-maxage:同max-age,覆盖max-age,Expires,但仅适用于共享缓存,在私有缓存中被忽略。
3. public:表明响应可以被任何对象(发送请求的客户端,代理服务器等等)缓存。
4. private:表明响应只能被单个用户(可能是操作系统用户,浏览器用户)缓存,是非共享的,不能被代理服务器缓存。
5. no-cache:强制所有缓存了该相应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存。
6. no-store:禁止缓存,每次请求都要向服务器重新获取数据。
协商缓存
缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。
浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回200就相当于重新请求了一遍资源并替换旧资源。最后,更新缓存头信息。
注意:如果响应头中有Last-modified而没有Expires或Cache-Control时,浏览器会有自己的算法来推算出一个时间缓存该文件多久,不同浏览器得出的时间不一样,所以Last-modified要记得配合Expires和Cache-Control使用。
当本地缓存过期后,客户端如何知到该使用上述哪种方式呢?其实,在客户端第一次请求资源时,服务器需要带上Last-Modified与ETag,然后缓存起来,当缓存失效重新进行新鲜度检查时,这两个头部就可以派上用场了。一般来说,ETag的优先级高于Last-Modified。
1、If-None-Match和Etag
Etag:是服务器端对于某个资源的某个特定版本的一个标识符
If-None-Match:如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变
If-None-Match: W/"162c-1582aaed638" & Etag: W/"162c-1582aaed638"
浏览器初次请求某个资源时,服务器http响应头中会添加一个Etag信息,当浏览器再次请求该资源时,会在request header中加入If-None-Match,其值就是之前收到的Etag值,如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag。
2、If-Modified-Since和Last-Modified
If-Modified-Since的值是浏览器端缓存内容的最后修改时间,属于请求头
Last-Modified的值是服务器端资源的最后修改时间,属于响应头
If-Modified-Since: Fir, 02 Dec 2016 17:20:11 GMT & Last-Modified: Fir, 02 Dec 2016 17:20:11 GMT
If-Modified-Since的值发送到服务器去,服务器会把这个时间与服务器上实际文件的Last-Modified的值进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中。
Etag和Last-modified区别
- 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
- Last-modified只能精确到秒。
- 一些资源的最后修改时间改变了,但是内容没改变,使用Last-modified看不出来内容没有改变。
- Etag的精度比Last-modified高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供Etag的话,必须先对Etag进行Conditional Request。
注意:实际使用Etag/Last-modified要注意保持一致性,做负载均衡和反向代理的话可能会出现不一致的情况。计算Etag也是需要占用资源的,如果修改不是过于频繁,看自己的需求用Cache-Control是否可以满足。
实际应用
回到实际应用上来,首先要明确哪些内容适合被缓存,哪些不适合。
考虑缓存的内容:css样式文件,js文件,logo,图标,html文件,可以下载的内容
不应该被缓存的内容:业务敏感的get请求
可缓存的内容又分为几种不同的情况:
不经常改变的文件:给max-age设置一个较大的值,一般是max-age=31536000;比如引入一些第三方文件,打包出来的带有hash后缀的css,js文件。一般来说文件内容改变了,会更新版本号,hash值,相当于请求另一个文件。
标准中规定max-age的值最大不超过一年,所以设成max-age=31536000。至于过期内容,缓存区会将一段时间不用的文件删除。
可能经常需要变动的文件:Cache-Control:no-cache / max-age=0
比如入口index.html文件,文件内容改变但名称不变的资源。选择Etag或Last-Modified来做验证,在使用缓存资源之前一定会去服务器端做验证,命中缓存时会比第一种情况慢一点点,毕竟还要发请求进行通信。
清除缓存
1. Math.random()
$.ajax({url:'test.php?'+parseInt(Math.random()*100000)}
可以采取在ajax的url后加上随机串来避免浏览器缓存,使每一次的请求都是一个新请求,从而防止浏览器从缓存中读取旧版本,
2. 加入当前时间
除了加入随机数还可以加入当前时间,同样可以使每次请求的地址都是不一样的,从而防止浏览器使用缓存。
3. ajax清除浏览器缓存
$.ajax({
url:'www.baidu.com',
dataType:'json',
data:{},
beforeSend:function(xmlHttp){
xmlHttp.setRequestHeader("If-Modified-Since","0");
xmlHttp.setRequestHeader("Cache-Control","no-cache");
},
success:function(response){
//操作
}
async:false
});
4. 直接用cache:false
$.ajax({
url:'www.haorooms.com',
dataType:'json',
data:{},
cache:false,
ifModified :true ,
success:function(response){
//操作
}
async:false
});