一、什么是浏览器缓存?
浏览器将请求过的资源(html、js、css、img)等,根据缓存机制,拷贝一份副本存储在浏览器的内存或者磁盘上。如果下一次请求的url相同时则根据缓存机制决定是读取内存或者磁盘上的数据还是去服务器请求资源文件。
缓存通过url来判断,如果url不同则是新的资源。所以我们开发时前端会将一些js、css等文件在后面加hash值来避免资源更新时浏览器仍读取缓存文件,导致需要刷新才能获取新的资源的问题。
这种做法虽然解决了js、css等文件的更新,这种加hash的就不在这篇文章说了,具体方法上网查即可,但是对于index.html这样的文件由于不能加hash值,则仍然会存在当资源更新了,但是仍然读取缓存文件的问题。
二、CDN的定义
CDN:Content Delivery Network/Content Ddistribute Network,即内容分发网络
客户端访问网站的过程:
没有CDN:
1、用户在浏览器访问栏中输入要访问的域名;
2、浏览器向DNS服务器请求对该域名的解析;
3、DNS服务器返回该域名的IP地址给浏览器;
建立TCP连接(Socket):三次握手,确保这个一定是一个有效的请求和响应,这个三次握手在业界相信大多数人都不陌生,虽然它是提高了传输的有效性,但是这个导致的直接问题就是整个传输过程是很耗时的,也就是说每次http请求都会经历三次握手这个过程,消耗的时间也是不言而喻,并且传统的http协议规定一次请求只能请求一个文件,所以一些顶级网站千方百计的采取一些减少http请求的策略,大多数就是采取一次http请求能够请求多个文件这样的实现,欣喜的是,http2.0已经支持能够一次http能够请求多个文件,这个还是值得期待全部推行开来的,只不过肯定需要过上一段时间,慢慢去等待推行吧。
4、浏览器使用该IP地址向服务器请求内容;
5、服务器将用户请求的内容返回给浏览器。
使用了CDN:
1、用户在浏览器中输入要访问的域名;
2、浏览器向DNS服务器请求对域名进行解析。由于CDN对域名解析进行了调整,DNS服务器会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器;
3、CDN的DNS服务器将CDN的负载均衡设备IP地址返回给用户;
4、用户向CDN的负载均衡设备发起内容URL访问请求;
5、CDN负载均衡设备会为用户选择一台合适的缓存服务器提供服务
选择的依据包括:根据用户IP地址,判断哪一台服务器距离用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器的负载情况,判断哪一台服务器的负载较小。
基于以上这些依据的综合分析之后,负载均衡设置会把缓存服务器的IP地址返回给用户;
6、用户向缓存服务器发出请求;
7、缓存服务器响应用户请求,将用户所需内容传送到用户;
如果这台缓存服务器上并没有用户想要的内容,而负载均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉取到本地。
三、关于缓存
没有CDN:浏览器缓存
使用了CDN:浏览器缓存+CDN缓存
在用户第一次访问网站后,网站的一些静态资源如图片等就会被下载到本地,作为缓存,当用户第二次访问该网站的时候,浏览器就会从缓存中加载资源,不用向服务器请求资源,从而提高了网站的访问速度,而若使用了CDN,当浏览器本地缓存的资源过期之后,浏览器不是直接向源站点请求资源,而是向CDN边缘节点请求资源,CDN边缘节点中也存在缓存,若CDN中的缓存也过期,那就由CDN边缘节点向源站点发出回源请求来获取最新资源。
浏览器缓存以及CDN缓存都有一套判断文件是否需要更新的机制:
浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器,当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器还是从自己的缓存中加载资源,当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。
CDN节点缓存机制在不同服务商中是不同的,但一般都遵循HTTP协议,通过http响应头中的Cache-Control:max-age的字段来设置CDN节点文件缓存时间。当客户端向CDN节点请求数据时,CDN会判断缓存数据是否过期,若没有过期,则直接将缓存数据返回给客户端,否则就向源站点发出请求,从源站点拉取最新数据,更新本地缓存,并将最新数据返回给客户端。CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。CDN缓存时间会对“回源率”产生直接的影响,若CDN缓存时间短,则数据经常失效,导致频繁回源,增加了源站的负载,同时也增大了访问延时;若缓存时间长,数据更新时间慢,因此需要针对不同的业务需求来选择特定的数据缓存管理。
浏览器缓存刷新:
1、在地址栏中输入网址后按回车或者点击转到按钮
浏览器以最少的请求来获取网页的数据,浏览器会对所有没有过期的内容直接使用本地缓存即使用强缓存,从而减少了对服务器的请求,Expires、max-age标志只对这种方式有效。
2、按F5或浏览器刷新按钮
浏览器会在请求中附加必要的缓存协商,但不允许浏览器直接使用本地缓存即跳过强缓存的判断,直接进行协商缓存的判断,Last-Modified、ETag在这种方式发挥作用。
3、按Ctrl+F5或按Ctrl并点击刷新按钮
强制刷新,完全不使用缓存
CDN缓存刷新:
CDN节点对开发者时透明的,可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN节点缓存的效果,强制使数据过期,从而获取到最新的数据。
四、浏览器缓存
这一点主要解析浏览器缓存以及缓存机制的详细过程。
与缓存相关的状态码:
200 ok | 从浏览器下载的最新资源 |
200 (from memory cache) | 不进行http请求,直接从浏览器内存中读取的资源,页面关闭,则资源释放,一般一些脚本、图片、文字等会存在内存中 |
200 (from disk cache) | 不进行http请求,直接从磁盘中读取的资源,页面关闭,资源仍然存在,除非清除缓存,一般一些非脚本文件会存在磁盘中,例如html、css文件 |
304 (not modified) | 请求了服务器,但是由于服务器资源没有更新,所以仍使用内存中的资源 |
缓存相关的http header介绍:
http header | 介绍 |
cache-control | response header or request header;指定缓存机制,优先级最高 |
expires | response header or request header;指定缓存的过期时间(现在浏览器一般设置cache-control,设置expires是为了兼容http1.0) |
last-modified | response header;资源的最后修改时间 |
etag | response header;资源的唯一标识符 |
if-modified-since | request header;缓存的服务器资源的最后修改时间 |
if-none-match | request header;缓存的服务器资源的唯一标识 |
1.1强缓存:
不会进行http请求,读取的是内存中的资源,直到缓存失效
涉及到的状态码:
- 200(from memory cache)
- 200(from disk cache)
涉及到的http header:
- cache-control
- expires
当浏览器对某个资源的请求命中了强缓存时,返回的http状态码为200,在chrome开发者工具中的network中的size会显示from cache
强缓存时利用Expires或者Cache-Control这两个http header实现的,都用来表示资源在客户端缓存的有效期
Expires是http1.0提出的一个header,描述的是一个绝对时间,由服务器返回,用GMT格式的字符串表示,如Exprires:Thu,31 Dec 2037 23:55:55 GMT
缓存过程:
1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上Expires的header
2、浏览器在接收到这个资源后,会把这个资源连同所有的response header一起缓存下来,所以缓存命中的请求返回的header并不是来自服务器,而是来自之前缓存的header
3、浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,拿出Expires跟当前的请求时间比较,如果请求时间在Expires指定的时间之前,就能命中缓存,否则就不行。
4、如果缓存没有命中,浏览器直接从服务器加载资源时,Expires Header在重新加载的时候会被更新
Expires是服务器返回的一个绝对时间,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如随意修改下客户端时间,就能影响缓存命中的结果,所以在http1.1的时候,提出了一个新的header,也就是Cache-Control,这是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,因此更有效安全一些,在配置缓存的时候,以秒为单位,用数值表示:如:Cache-Control:max-age=315360000,它的缓存过程是:
1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上Cache-Control的header
2、浏览器在接收到这个资源的时候,会把这个资源连同所有response header一起缓存下来
3、浏览器再次请求这个资源的时候,先从缓存中寻找,找到这个资源之后,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行。
4、如果缓存没有命中,浏览器直接从服务器加载资源时,Cache-Control在重新加载的时候会被更新
- cache-control
优先级最高,所有的缓存机制看到 cache-control 都要服从它;在请求头和响应头中都可以设置;cache-control有多种设置,设置如下:
cache-control | 描述 |
no-store | 请求和响应都不缓存(禁用) |
no-cache | 协商缓存,相当于cache-control:max-age=0(每次取之前先跟服务器沟通比较) |
max-age | 指定缓存内容将在xxx秒后失效 |
public | 所有内容都将被缓存(客户端和代理服务器都可缓存) |
private | 所有内容只有客户端可以缓存,Cache-Control的默认取值 |
注意:这个header可以只用一个,也可以同时用两个,同时存在时,Cache-Control优先级高于Expires
1.2 强缓存的管理
两种方式来设置是否启用强缓存:
1、通过代码的方式,在web服务器返回的响应中添加Expires和Cache-Control Header
2、通过配置web服务器的方式,让web服务器在响应资源的时候统一添加Expires和Cache-Control Header
1.3 强缓存的应用
强缓存是前端性能优化最有力的工具,对于有大量静态资源的网页,一定要利用强缓存,提高响应速度,通常是为这些静态资源全部配置一个超时时间超长的Expires或Cache-Control,这样用户只会在第一次访问网站时加载静态资源,其他时间只要缓存没有失效并且用户没有强制刷新的条件下都会从缓存中加载。
然而这种缓存配置方式会带来一个问题,就是当资源更新时,客户端由于有缓存不会向服务器请求最新的资源,这个问题已有解决方案:
通过更新页面中引用的资源路径,让浏览器主动放弃缓存,加载新资源。
但要实现有更新的文件才需要浏览器重新加载,因此必须让url的修改与文件内容相关联,利用数据摘要算法对文件求摘要信息,摘要信息与文件内容一一对应,这一点许多前端构建工具都做到了,如webpack
1.4 浏览器默认缓存使开发环境下常因为资源没有及时更新而看不到效果
解决方法:
1、ctrl+F5
2、浏览器隐私模式开发
3、chrome开发者工具里将Disable cache选项打勾,阻止缓存
4、在开发阶段,给资源加上一个动态的参数,由于每次资源的修改都要更新引用的位置,同时修改参数的值,所以操作起来不是很方便,除非是在动态页面比如jsp里开发就可以用服务器变量来解决,或者用前端构建工具来处理这个参数修改的问题。
5、如果资源引用的页面被嵌入到了一个iframe里面,可以在iframe的区域右键重新加载该页面
6、如果缓存问题出现在ajax请求中,最有效的解决办法就是ajax的请求地址追加随机数
7、动态设置iframe的src时,有可能因为缓存问题导致看不到最新效果,在src后面添加随机数即可
8、通过前端开发工具grunt gulp等的插件来启动一个静态服务器,则在这个服务器下所有资源返回的response header中,Cache-Control始终被设置为不缓存
1.5 发布问题
发布问题:若页面和它引用的资源路径同时更新了,不管是先部署页面还是先部署资源都会带来各种问题,这是由于资源是覆盖式发布的,即用待发布资源覆盖已发布资源。
解决办法就是实现非覆盖式发布:把有修改的资源文件作为一个新的文件发布,不对已有的资源文件进行覆盖,这样用户还可以请求旧的资源文件,不至于发生页面错乱的问题,这样先部署静态资源,再覆盖式部署页面,等到用户访问新页面的时候,新的资源文件也已发布,就可以正确请求,即可解决问题。
2.1 协商缓存
涉及到的状态码:
- 304 not modified
- 200 ok
涉及到的请求头
- etag / ig-none-match
- last-modified / if-modified-since
如果命中协商缓存,请求响应返回的http状态为304以及一个Not Modified字符串,协商缓存利用的是【Last-Modified、If-Modified-Since】、【ETag、If-None-Match】这两对header来管理的。
【Last-Modified、If-Modified-Since】:
1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源时,在response的header加上Last-Modified的header,表示这个资源在服务器上的最后修改时间
2、浏览器再次向服务器请求这个资源时,在request的header加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值
3、服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容,如果有变化就返回资源内容,当服务器返回304 Not Modified的响应时,response header中不会再添加Last-Modified的header,因为资源没有变化,Last-Modified的值也不变
4、浏览器收到304的响应后,就会从缓存中加载资源
5、如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modofied header在重新加载的时候会被更新,下次请求时,If-Modified-Since会采用上一次返回的Last-Modified的值
这一对header都是根据服务器时间返回的,有时候会有服务器资源有变化,但最后修改时间却没有变化的情况,因此有了
【Etag、If-None-Match】:
1、浏览器第一次向服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识,是一个字符串,只要资源内容发生改变,这个字符串也会改变,跟时间没有关系
2、浏览器再次请求这个资源的时候,在request的header上加上If-None-Match的header。这个header的值是上一次请求返回的ETag的值
3、服务器再次收到资源请求时,根据客户端传过来的If-None-Match和重新生成的该资源的新的ETag做比较,相同则返回304 Not Modified,不会返回资源内容,如果不同则返回资源内容,但这里即使资源没有发生变化,也会返回ETag,因为这个ETag重新生成过,即使没有ETag没有变化
4、浏览器收到304响应后,就从缓存中加载资源
etag两种类型:
强etag:
不论实体发生多么细微的变化都会改变其值
强ETag表示形式:"22FAA065-2664-4197-9C5E-C92EA03D0A16"。
弱etag:
弱 ETag 值只用于提示资源是否相同。只有资源发生了根本改变产 生差异时才会改变 ETag 。这时,会在字段值最开始处附加 W/。
弱ETag表现形式:W/"22FAA065-2664-4197-9C5E-C92EA03D0A16"。
注意:当etag和last-modified同时存在时则以etag为准
2.2 协商缓存的管理
一般服务器上的【Last-Modified、If-Modified-Since】和【Etag、If-None-Match】会同时启用,协商缓存需要配合强缓存使用
五、实际用途
本人在做一个资讯交流类的vue项目中,有一个需求是点击资讯的删除按钮,将会删除这条资讯,听起来很简单,点击删除后,带上id去请求删除的接口,并将dom删除,但是问题来了,我确实删了这条资讯,dom也删除了,但当我点击了其他资讯再点击返回进入这个页面时,它又在视图里出现了!!!再刷新后,它又没了??
找了半天原因,原来是因为浏览器启用了from disk cache(从磁盘缓存中获取)这个东西,那怎么禁用它呢,直接上代码:
禁用缓存 headers: {‘Cache-Control’: ‘no-store’}
axios
axios.get('你的接口', {headers: {'Cache-Control': 'no-store'}})
// 或者
const http = axios.create({
headers: {'Cache-Control': 'no-store'}
})
有时候缓存是 200 OK (from disk cache)有时候会是 304 ? 看运维是否移除了 Entity Tag。移除了,就总是 200 OK (from cache)。没有移除,就两者交替出现。
他们两个的区别是 200 OK (from disk cache) 是浏览器没有跟服务器确认, 就是它直接用浏览器缓存。
304 是浏览器和服务器确认了一次缓存有效性,再用的缓存。
那么禁止200 OK (from disk cache) 这个缓存的方法是,ajax 请求是带上参数 cache: false
六、项目遇到的问题
1.项目上线后用户需手动刷新页面才能获取新的资源
产生原因:http请求没有设置缓存机制(cache-control或者expires),导致浏览器不知道以什么方式缓存。这种情况一般默认为强缓存,强缓存时间根据一定的计算方式获得,在这个时间段内不会进行网络请求,返回的状态码为200(from disk cache)或者200(from memory cache)
解决办法:
针对不能加hash值又想要随时获取最新资源的html文件,应该设置请求头 cache-control:no-cache,相当于cache-control:max-age=0
2.奇怪的问题与排查
现象:nginx并没有配置cache-control或者expires,但是有时却不需要刷新就可以返回最新的资源(200 ok),而有时则返回200 from disk cache。
排查:
查看http的header信息,发现差别在if-modified-sine这个请求头上,返回200 ok的请求头会携带if-modified-sine,而返回200 from disk cache的请起头中没有携带if-modified-sine。
查了一些资料终于找到原因,原因如下:
if-modified-sine的值是第一次报文中 last-modified 的值
为什么会有条件请求字段呢?是因为缓存过期了,所以浏览器会从缓存中查找是否有etag、last-modified字段(注意,缓存是缓存的整个报文,而不仅仅是body部分),有的话,就在请求中带上,向服务器发起协商缓存请求。如果服务器发现资源没有改变,就返回304响应,浏览器就知道,本地缓存中的这个数据资源可以继续使用。(304响应是没有body体的,只有头字段等元信息)也就是说,只有在缓存过期的情况下,请求报文中才会有条件请求的相关字段。什么情况下,缓存会过期呢?如下:
cache-control: max-age=0
cache-control: no-cache
响应报文根本就没有返回任何关于cache有效期的头字段: cache-control / expire / progma 。那么看是否返回了 last-modified ,如果有该header,浏览器可以使用 Heuristic 算法计算出一个通用的缓存时间 (Date - last-modified) * 10%