浏览器的缓存机制小结
什么是浏览器缓存?
浏览器缓存(Brower Caching)是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。
我们可以通过 chrome://view-http-cache/ 来查看chrome浏览器缓存了什么内容。
推荐阅读文章:大公司里怎样开发和部署前端代码?
为什么要用浏览器缓存?
- 第一:避免了冗余的数据传输,节省流量。
- 第二:加快了用户访问网页的速度。
- 第三:减小了服务器的压力。
注意,一般,我们缓存的都是css、js、图片之类的文件,这些文件的改动比较小,而html文件是不缓存的,因为html文件中常常包含动态的内容,比如引入a.css?v=1.0,最后是版本号,如果html也被缓存,那么我们就没办法达到通过修改版本号进而使得用户获取新内容的目的了。
浏览器缓存有哪些类型?
缓存类型分为强缓存和协商缓存。
强缓存:在用户请求资源时,如果命中强缓存,则不向服务器请求,而直接从本地获取资源。我们可以看到200状态码,并提示from disk cache或from memory cache(区别后面讲)。
协商缓存: 在用户请求资源时,浏览器直接则向服务器发送请求,服务器根据 request header 来判断是否命中协商缓存,如果命中,则返回304和新的response header,使用本地资源;否则,返回新的资源。
强缓存和协商缓存的共同点:两者命中后都是从本地读取资源。
强缓存和协商缓存的区别: 强缓存很强势,是没有向服务器发出请求的; 而协商缓存必须要向服务器发一个请求来协商。
浏览器缓存有关的header
强缓存
强缓存是利用http的响应头中的Expires字段和Cache-Control两个字段来控制的,用来表示使用缓存的有效时间。
Expires
Expires是http1.0规范的,表示缓存的过期时间。 如某个资源的response heade中的字段: Expires: Fri, 18 Aug 2017 07:57:17 GMT。 表示当浏览器再次加载这个资源时,如果时间没有超过,就命中强缓存,使用内存中缓存的资源。
之所以浏览器在再次加载时可以判断出时间是否超过,是因为浏览器在缓存资源时,不仅缓存了资源,还缓存了response header相关的内容,比如这里Expires字段。
缺点:由于不能保证服务器和用户端的绝对时间保持一致,所以缓存有时可能会出现混乱的情况, 在HTTP1.1版本中开始使用Cache-Control的方法进行缓存。
Cache-Control
Cache-Control是http1.1规范的,同样表示缓存的过期时间。 其中的max-age是作为判断是否过期的主要判据,它是一个相对时间,单位为s。 如知乎上的某一张图片的response header中的字段:cache-control: public, max-age=31536000。 public代表了这张图片是可以被任何用户缓存的,包括代理服务器等; 而max-age是表示在31536000s(一年)内,如果再次请求就使用本地资源。
Cache-Control除了max-age的使用之外,还有几个比较重要的字段:
- no-cache: 不优先使用本地缓存,而是使用协商缓存。注意: 这里并不是说一定不适用本地缓存的资源,而是需要先协商一下,如果命中,还是会使用本地缓存的。
- no-store:一定不使用本地缓存,每次用户请求资源,都会下载得到服务器发来的最新的资源。
- public:资源可以被任何用户缓存,包括所有普通用户和代理服务器。
- private:只能被当前的特定用户缓存,其他用户无法缓存。 一般是说代理服务器不能缓存。
强缓存之Expires、Cache-Control比较
相同点: 两者都是强缓存。
不同点:
- Expires是http1.0规定的,而Cache-Control是http1.1规定的。
- Expires的过期时间采用的是绝对时间,容易造成差错; 而Cache-Control的过期时间采用的时相对时间,在缓存上不会出现问题。
- 两者可以同时存在于一次请求中,但是不会同时在一次请求中起作用。 在HTTP1.0的环境下,Cache-Control不起作用,Expires起作用; 在HTTP1.1的环境之下, Expires不起作用,而Cache-Control起作用。当前一般都是http1.1的情况,所以Expires是作为一种向下兼容的形式而存在的。
- Cache-Control的选择更多,功能更为强大,推荐使用。 Expires作为强缓存,功能单一,不推荐使用。
协商缓存
协商缓存一般是使用 if-modified-since/last-modified 和 if-none-match/etag 由服务器来决定浏览器缓存的资源是否可以使用。
if-modified-since/last-modified
在用户请求到资源之后,会返回这个资源,并且在response header中返回一个 last-modifed 字段,这时浏览器就会缓存这个资源以及最后的修改时间, 可以是: last-modified: Fri, 18 Aug 2017 07:27:24 GMT。 接着,当用户再次请求相同的资源时,需要在请求头中添加 if-modified-since 字段,这个字段的值就是之前存储的 last-modifed 的值,服务器得到 if-modified 值之后,会和资源最近的修改时间作比较,如果命中,则返回304,让浏览器使用缓存的资源;否则,返回一个最新的资源并且在 last-modified 修改为最近的资源修改时间。
if-none-match/etag
在用户请求到资源之后,会返回这个资源,并且在response heade 中返回一个 etag 字段,即 entity tag,这个字段的值是一个字符串,唯一的标识了这个资源,只要资源发生了变化,这个etag值就会发生变化。当用户再次请求资源时,会在request header中携带 if-none-match 字段,其值为上次缓存的 etag 值,如果命中,则返回304,使用缓存资源;否则,服务器返回最新的资源。
两种协商缓存机制的比较
相同点: 都是为了协商缓存。
不同点:
- 在精度上,Etag优于last-modified。 如果一个文件在1s内改变了很多次,通过etag是可以判断出来并返回最新的资源的,但是last-modifed的精度只能到s,是无法返回最新资源的,准确地说,UNIX记录只能精确到s。
- 在准确率上,Etag优于last-modified。有些文件可能整体copy等,只是在时间上发生了变化,而内容上并没有发生变化(etag变化,last-modified不变),如果使用last-modified,那么就会返回最新的资源,实际上这是不需要的。
- 在性能上,last-modified优于Etag。因为last-modified只需要记录时间,而etag需要重新由服务器生成一个hash值,所以在性能上etag略差。
- 在优先级上,Etag优于last-modified。 也就是说,etag和last-modified是可以同时使用的,但是到服务器端,会优先判断etag,如果相同,直接返回304;如果不同,就继续比较last-modified,然后再决定是否返回新的资源。
浏览器缓存过程是怎样的?
- 浏览器第一次加载资源,服务器返回200, 浏览器将资源下载下来,把资源和response header相关内容一并缓存。
- 下一次加载时,首先比较cache-control,如果没有超过时间,则命中强缓存,不发送请求,直接读取本地文件(如果不支持http1.1,则使用expires来判断);如果时间已经过期,则发送带有if-none-match和if-modified-since的请求头。
- 服务器接受到请求之后,首先判断etag是否和服务器上文件的etag一致,如果一致,则命中协商缓存,返回304;如果不一致,返回新的资源并带上新的etag值返回200。
- 如果请求中没有etag值,则比较发送来的 if-modified-since 值,如果命中,则返回304,;否则,返回新的资源带上新的last-modified的值并返回状态码200。
用户行为对浏览器缓存的控制如何?
- 地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制;
- F5刷新,浏览器会设置max-age=0,跳过强缓存判断,进行协商缓存判断;
- ctrl+F5刷新,跳过强缓存和协商缓存,直接从服务器拉取资源。
实际问题分析
代码更新到线上后用户浏览器不能自行更新,我们不能要求客户在系统更新后都进行一次缓存清理的操作。
在资源请求的URL中增加一个参数,比如:js/mian.js?ver=0.7.1。这个参数是一个版本号,每一次部署的时候变更一下,当这个参数变化的时候,强缓存都会失效并重新加载。这样一来,静态资源,部署以后就需要重新加载。这样就比较完美的解决了问题。
网站的缓存类型?
第一级、浏览器缓存
优点:
这是最省资源的方式,浏览器甚至都不需要发起请求。
缺点:
缓存脱离控制,如果缓存时间太长,用户无法访问最新的页面,特别是出现一个紧急的bug的时候,需要一段时间,才能平息。
使用:
在header头部添加Expires、Cache-Control、max-age、last-modified、etag的一个或者是多个,可以在meta中对缓存进行控制。
建议:
js、css、图片可以把缓存时间设置的久一些,永远不过期都是可以的,文件出现变化的时候,通过更换(添加)版本号就行更新。
第二级、CDN缓存-静态页面
根据智能DNS解析,用户访问到最近的一台机器,这样就可以减少网络延迟。
用户访问CDN,CDN上如果资源存在并且没有过期,直接返回给用户内容,否则需要先访问源站,然后保存到CDN服务器上面,然后返回用户。
优点:
(1)请求分散,削弱了高流量下的压力。
(2)减少了网络延迟。
缺点:
(1)节点资源可能缓存不一致,导致不同的用户看到的结果不同。
(2)如果设置过期时间短或者其他的不合理的回源策略(即又访问源服务器),会导致大量回源,导致访问速度变慢。
(3)高并发期间,一个资源,可能会有多个回源请求。
(4)刷新CDN,需要一段时间, 如果出现bug, 也得登上几分钟。
第三级、proxy缓存-动态页面
很多代理服务器都支持了缓存,比如Nginx,Apache。一些动态页面或者接口不怎么更新,比如产品列表等,可以把内容缓存到服务器里面,下次请求的时候,直接读取缓存,动态脚本都不需要执行。动态脚本如果要返回的话,可能需要访问数据库,以及一些其他的服务,这就增加了相应时间。
使用:
nginx,可以配置 proxy_cache
apache,可以配置 mod_cache、mod_disk_cache、mod_file_cache以及mod_mem_cache
第四级、opcode缓存
是针对php代码。php执行的时候,需要先对php代码解释,生成中间代码,称为opcode,然后再执行。如果缓存住,就可以不需要每一次都解释,这样就可以节省不少资源,增加吞吐量。
优点:
节省资源,增加吞吐量。
缺点:
(1)代码更新,需要等待opcode失效,如果设置不过期,那就需要重启服务器。
(2)opcode主要节省了CPU和内存资源,如果时间消耗主要是磁盘或者是网络IO,那么作用不是太大。
第五级、文件缓存
会把计算或者从数据库,以及其他服务获得数据,放到一个文件里面,使用的时候取出来。常见的session就是这么做的。我看phpcms就大量使用这种缓存。
缺点:
1)多台服务器,数据缓存内容可能不一致。为了解决这个问题,曾经挂载共享代理,这就导致磁盘IO成为瓶颈。
2)设置过期时间,不太容易。
第六级、分布式缓存
为了解决文件缓存存在的问题,就出现了分布式缓存。常见的有memcache,redis.可以搭建集群,设置过期时间。redis,百度都把它当做数据存储来使用。这种缓存在互联网行业当中,大量应用。
缺点:
需要人维护,消耗大量内存,需要设置过期时间