前言
- 浏览器缓存是web性能优化的重要方式:
- 减少冗余的数据传输
- 减少服务器负担
- 加快客户端加载网页的速度
- 浏览器一般都是通过http缓存的,但是也可以通过其他方式,如在html页面中的meta标签增加属性,但不是所有浏览器都支持的。
- 大多数的浏览器缓存都是有http的header所决定的,请求头和响应头。
浏览器缓存规则
对于浏览器端的缓存来讲,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
- 新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的:
含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度。满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。
- 校验值(验证机制):
服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
- 缓存命中率:
一个缓存的有效性是依照缓存的命中率来度量。它是根据得到数据请求次数与所有请求次数的比率。缓存命中率高意味着有很高的比率数据是从缓存中获取到数据的。
- 在HTTP请求和响应的消息报头中,常见的与缓存有关的消息报头有:
缓存流程
- 首次加载:
- 浏览器第一次加载资源时,必须下载所有资源,然后根据响应的header内容来决定如何缓存资源,可能采用的是强缓存,也可能是弱缓存。
- 浏览器第一次加载资源时,必须下载所有资源,然后根据响应的header内容来决定如何缓存资源,可能采用的是强缓存,也可能是弱缓存。
- 后续请求:
由图可知,浏览器请求静态资源时的HTTP流程:
- 强缓存阶段:先在本地查找资源,如果发现该资源,并且判断其他限制是否有问题(expire或cache-control等判断是否命中强缓存),若命中强缓存,返回200,直接使用强缓存,并且不会发送请求到服务器
- 弱缓存阶段:若在本地发现该资源并且没有命中强缓存,则发送一个HTTP请求到服务器,服务器判断这个资源是否被改动过,若无改动,返回304,让浏览器使用该资源。
- 缓存失败阶段(重新请求):当服务器发现该资源被修改过,或者在本地没有找到该缓存资源,服务器返回该资源的数据。
强缓存与弱缓存的区别
- 获取资源形式:都是从缓存中获取资源
- 强缓存:200,弱缓存:304
- 请求:强缓存不发请求,直接从缓存中读取,弱缓存发送请求,若验证文件没有被修改,返回304
强缓存
-
强缓存是利用Expires或者Cache-Control,让原始服务器为资源设置一个过期时间,在多长时间内可以将这些内容视为最新的。
-
若时间未过期,则命中强缓存,使用缓存文件不发送请求。
-
两种方式:Expires+Pragma 与 Cache-Control
Cache-Control
-
Cache-Control是HTTP1.1中为了弥补HTTP1.0中Expires的缺陷而加入的,当Expires和 Cache-Control同时存在时, Cache-Control的优先级高于Expires。
-
可缓存性:
-
public:表示响应可以被任何对象缓存(包括发请求的客户端、代理服务器等)。
-
private:只有用户自己的浏览器能够进行缓存,公共的代理服务器不能缓存。
-
no-cache:强制浏览器在使用cache拷贝之前先提交一个HTTP请求到源服务器进行确认,HTTP请求没有减少会减少一个响应体(文件内容),这个选项类似于弱缓存。
-
only-if-cache:表明客户端只接收已缓存的相应,并且不要向原始服务器检查是否有更新的拷贝。
-
-
到期设置:
- max-age=60:设置缓存存储的最大周期超过这个时间缓存被认为过期(单位秒)
-
其他设置:
-
no-store:告诉浏览器在任何情况下都不要进行cache,不要在本地保留拷贝。
-
must-revalidate:缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源
-
HTTP1.0时代的缓存 Expires+Pragma
- Expires用于设置缓存到期时间:
指定缓存到期的GMT绝对时间,如果设置了max-age就会覆盖expires,如果expires到期需要重新请求 Expires:Sat, 09 Jun 2018 08:13:56 GMT
- Pragma禁用缓存
Pragma:no-cache 表示防止客户端缓存,需要强制从服务器获取最新数据。
强缓存命中 from memory cache &from disk cache
- from memory cache:部分资源缓存到内存中,从内存中读取
- from disk cache:部分资源缓存到磁盘中,从磁盘中读取
- 区别:当退出进程时,内存中的数据会被清空,而磁盘的数据不会,内存中读取更快,磁盘中涉及I/O操作,也非常快了
弱缓存
- 如果强缓存过期,或者没有设置,导致未命中的话,就进入弱缓存阶段了。
- 两种方式:Last-Modified & If-Modified-Since 与 ETag & If-None-Match
Last-Modified & If-Modified-Since:
- Last-Modified & If-Modified-Since是一对报文头,属于HTTP1.0
- 1、Last-Modified:GMT时间,是服务器认为文件的最后修改时间,是第一次请求文件时,服务器返回的一个属性。
Last-Modified: Sat, 09 Jun 2018 08:13:56 GMT
- 2、第二次请求这个文件时,浏览器把If-Modified-Since发送给服务器,询问该时间后文件是否被修改过。
If-Modified-Since: Sat, 09 Jun 2018 08:13:56 GMT // 跟Last-Modified的值一样
ETag & If-None-Match
- ETag & If-None-Match是一对报文,属于HTTP1.1。
- ETag是一个文件的唯一标识符,每个文件都有一个单独的标志,只要这个文件发生改变,这个标志就会发生变化。
- ETag机制类似于乐观锁机制,如果请求报文的ETag与服务器的的不一致,则表示该资源已被修改过,需要发最新的资源给浏览器。
- 与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
- 1、ETag也是首次请求的时候,服务器返回的。
ETag: "8F759D4F67D66A7244638AD249675BE2" // 长这样
- 2、第二次请求该资源时,浏览器将If-None-Match发送到服务器的验证,判断文件是否改变。
If-None-Match: "8F759D4F67D66A7244638AD249675BE2" // 跟ETag的值一样
ETag/Last-Modified过程如下:
- 客户端第一次向服务器发送请求,服务器将附加ETag/Last-Modified到所提供的资源上去
- 当再一次请求资源,如果没有命中强缓存,走弱缓存阶段在执行验证时,将上次请求时服务器返回的ETag/Last-Modified(If-None-Match/If-Modified-Since)一起传递给服务器
- 服务器检查If-None-Match/If-Modified-Since,并判断该资源自上次客户端请求之后是否被修改,若值一致,未修改,返回304和一个空的响应体。
同时使用这两种报文头
- 同时使用这两种报文头,则都命中才会命中弱缓存,否则将重新请求资源
- 同时使用这两种报文头,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
ETag主要是解决Last-Modified无法解决的一些问题:
- 一些文件也许内容并未改变(可能仅仅改变了修改时间),这时候我们不希望文件被重新加载,此时,ETag值会触发缓存,Last-Modified不会。
- If-Modified-Since能检查到的粒度是秒级的,当修改非常频繁时, Last-Modified会触发缓存,而ETag不会触发缓存,需重新加载。
- 某些服务器不能精确的得到文件的最后修改时间。
用户操作行为与缓存
- F5普通刷新:导致强缓存失效。
- Ctrl+F5强制刷新:强缓存、弱缓存都失效。
- 普通刷新会启用弱缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存,这也是为什么有时候我们更新一张图片、一个js文件,页面内容依然是旧的,但是直接浏览器访问那个图片或文件,看到的内容却是新的。
注意-在客户端设置 Pragma、Expires
- Pragma属于通用首部字段,在客户端上使用时,常规要求我们往html上加上这段meta元标签
<meta http-equiv="Pragma" content="no-cache">
- 它告诉浏览器每次请求页面时都不要读缓存,都得往服务器发一次请求才行。BUT!!! 事实上这种禁用缓存的形式用处很有限:
- 仅有IE才能识别这段meta标签含义,其它主流浏览器仅能识别“Cache-Control: no-store”的meta标签(见出处)。
- 在IE中识别到该meta标签含义,并不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求(仅限页面,页面上的资源则不受影响)。
- Expires属于通用首部字段,在客户端上使用时,常规要求我们往html上加上这段meta元标签
<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">
- 在客户端我们同样可以使用meta标签来知会IE(也仅有IE能识别)页面(同样也只对页面有效,对页面上的资源无效)缓存时间:
如果希望在IE下页面不走缓存,希望每次刷新页面都能发新请求,那么可以把“content”里的值写为“-1”或“0”。 注意的是该方式仅仅作为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段。
总结
拓展
服务器缓存
-
CDN缓存
-
CDN缓存,也叫网关缓存、反向代理缓存。浏览器先向CDN网关发起WEB请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态地请求转发到合适的源服务器上。
-
CDN缓存策略:
CDN边缘节点缓存策略 因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的Cache-control: max-age的字段来设置CDN边缘节点数据缓存时间。
-
当客户端向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向源站发出回源请求(back to the source request),从源站拉取最新数据,更新本地缓存,并将最新数据返回给客户端。
-
CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。
-
CDN缓存时间会对“回源率”产生直接的影响。若CDN缓存时间较短,CDN边缘节点上的数据会经常失效,导致频繁回源,增加了源站的负载,同时也增大的访问延时;若CDN缓存时间太长,会带来数据更新时间慢的问题。开发者需要增对特定的业务,来做特定的数据缓存时间管理。
-
CDN缓存刷新CDN边缘节点对开发者是透明的,相比于浏览器Ctrl+F5的强制刷新来使浏览器本地缓存失效,开发者可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制CDN节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。
-
-
CDN优缺点:
CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低; 大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。 当网站更新时,如果CDN节点上数据没有及时更新,即便用户再浏览器使用Ctrl +F5的方式使浏览器端的缓存失效,也会因为CDN边缘节点没有同步最新数据而导致用户访问异常。
-
-
Combo服务:
- Combo服务
Combo服务,也就是我们在最终拼接生成页面资源引用的时候,并不是生成多个独立的link标签,而是将资源地址拼接成一个url路径,请求一种线上的动态资源合并服务,从而实现减少HTTP请求的需求。 /??fle1,file2,file3,...的url请求响应就是动态combo服务提供的,它的原理很简单,就是根据url找到对应的多个文件,合并成一个文件来响应请求,并将其缓存,以加快访问速度。
- 但它也存在一些缺陷:
浏览器有url长度限制,因此不能无限制的合并资源。 如果用户在网站内有公共资源的两个页面间跳转访问,由于两个页面的combo的url不一样导致用户不能利用浏览器缓存来加快对公共资源的访问速度。如果combo的url中任何一个文件发生改变,都会导致整个url缓存失效,从而导致浏览器缓存利用率降低
HTML5缓存思路
- HTML5离线应用缓存manifest:
- 用户可离线访问你的应用,这对于无法随时保持联网状态的移动终端用户来说尤其重要
- 用户访问本地的缓存文件,通常意味着更快的访问速度
- 仅仅加载被修改过的资源,避免同一资源对服务器多次的请求,大大降低了对服务器的访问压力
- manifest文件罗列了需要被缓存的文件清单。
- 这个过程中有几个问题需要注意:
- 如果服务器对离线的资源进行了更新,那么必须更新manifest文件之后这些资源才能被浏览器重新下载,如果只是更新了资源而没有更新manifest文件的话,浏览器并不会重新下载资源,也就是说还是使用原来离线存储的资源。
- 对于manifest文件进行缓存的时候需要十分小心,因为可能出现一种情况就是你对manifest文件进行了更新,但是http的缓存规则告诉浏览器本地缓存的manifest文件还没过期,这个情况下浏览器还是使用原来的manifest文件,所以对于manifest文件最好不要设置缓存。
- 浏览器在下载manifest文件中的资源的时候,它会一次性下载所有资源,如果某个资源由于某种原因下载失败,那么这次的所有更新就算是失败的,浏览器还是会使用原来的资源。
- 在更新了资源之后,新的资源需要到下次再打开app才会生效,如果需要资源马上就能生效,那么可以使用window.applicationCache.swapCache()方法来使之生效,出现这种现象的原因是浏览器会先使用离线资源加载页面,然后再去检查manifest是否有更新,所以需要到下次打开页面才能生效。
localStorage
- 与sessionStroage主要的区别是存储时间和作用域。
- 另外,严格说来localStorage更像是cookie一类的本地数据存储。但在标准缓存之外,开发人员可以用浏览器的一些功能来实现自定义的客户端“缓存”。
参考链接
obkoro1.com/2018/06/09/… louiszhai.github.io/2017/04/07/… imweb.io/topic/55c6f…