前言
在前端开发中,一个网站的性能最直观体现就是网页打开的速度,而提高网页反应速度的一个方法就是使用缓存。
Web 缓存常用来保存一些常见的资源文件,当 Web 请求抵达资源时,如果本地有“已缓存的”文件,就可以直接从本地读取而不用从原始服务器中提取这个文档。通过设置 Web 缓存,可以达到几点优化:
- 减少冗余的数据传输;
- 缩短网页请求资源的距离,减少延迟;
- (由于缓存文件可以重复使用)可以减少带宽,降低网络负荷,缓解网络瓶颈。
Web 缓存分为很多种,比如数据库缓存、代理服务器缓存、还有我们熟悉的 CDN
缓存,以及浏览器缓存。 这里主要说下传说中的浏览器缓存。
浏览器缓存
浏览器页面的缓存状态是由 header 决定的,关于缓存的 header 常见有四种,而四种 header 其实也把缓存策略分为了本地缓存和协商缓存(再验证)两个阶段;分别为:设置是否进行本地缓存:Cache-Control
、Expires
,设置协商缓存策略:If-Modified-Since
、If-None-Match
。
本地缓存:
启用本地缓存时,选用其中一个首部即可,推荐使用较新的 Cache-Control 。如果同时使用 Expires 和 Cache-Control 首部,那么浏览器将以优先值更高的 Cache-Control 为准。
如果文件是通过缓存获得的,network 上该资源的请求会显示
200 OK (from disk cache)
,此时该请求是不会发送到原始服务器的。
一、Cache-Control:
首部:
HTTP/ 1.1 200 OK
Date: Sat, 27 Nov 2019, 21:30:00 GMT
content-type: text/plain
Content-length: 1958
Cache-Control: max-age=5201314
-
max-age
:指定设置缓存的最大的有效时间。浏览器发送过该请求后,在max-age
这段时间里就都不会向服务器发送请求了。( 即使服务器上的资源发生了变化,浏览器也不会得到通知。 ) -
s-maxage
(单位为 s)同max-age
,只用于共享缓存(比如CDN
缓存)。也就是说
max-age
用于普通缓存,而s-maxage
用于代理缓存。如果存在s-maxage
,则会覆盖掉max-age
-
public
:指定响应文档会被缓存,并且可以在多用户(如浏览器)间共享。如果没有指定 public 还是 private,则默认为 public。 -
private
:响应只作为私有的缓存,不能在用户间共享。如果有要求HTTP
认证,响应会自动设置为private
。 -
no-cache
: 指定不缓存响应,表明资源不进行缓存但是设置了
no-cache
之后并不代表浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。因此有的时候只设置no-cache
防止缓存还是不够保险,还可以加上private
指令,将过期时间设为过去的时间。 -
no-store
:禁止缓存,相比于上面用no-chache用了这个命令防止不缓存当然要稳得多拉~每次请求资源都要从服务器重新获取。 -
must-revalidate
:可配置缓存,这个响应首部告诉缓存,在实现没有跟原始服务器进行再验证的情况下,不能使用过期的缓存文档。而服务器可以通过返回304 Not Modified
让客户端可以继续使用过期的缓存的文档,以提高性能。当然,服务器也可以随意提供新鲜的副本。如果在缓存进行must-revalidate
新鲜度检查时,原始服务器不可用,缓存就必须返回一条504 Gateway Timeout
错误。
二、Expires
首部:
HTTP/ 1.1 200 OK
Date: Sat, 27 Nov 2019, 21:30:00 GMT
content-type: text/plain
Content-length: 1958
Expires Fri, 05 Jul 2019, 14:30:00 GTM
相对于 Cache-Control,Expires
是一个较老的首部(HTTP/1.0), 需要和 Last-modified 结合使用 ,其接受一个 Date 值指定文件的过期日期(绝对日期)。浏览器判断文件是否过期时,对比的是用户机器上的时间而不是服务器上的时间。所以使用 Expires 首部可能会出现的一个问题就是,用户本地时间是会影响到原先的缓存意图的。
注意:Cache-Control
的优先级高于Expires
。
协商缓存:
一、 Last-modified和If-Modified-since
- 基于客户端和服务器端协商的缓存机制
last-modified
-->response header
if-modified-since
-->request header
- 需要与
cache-control
共同使用
Last-modified
为服务端文件的最后修改时间,由服务端响应。
If-Modified-since
是一个条件式请求首部, 服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200
。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified
响应,而在 Last-Modified
首部中会带有上次修改时间。
If-Modified-since
示例:
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
二、Etag 和 If-none-match
- 文件内容的hash值
etag
-->reponse header
if-none-match
-->request header
- 需要与
cache-control
共同使用
根据当前文件内容生成一段 hash
字符串( 通常可以是一个 MD5
值 ),唯一标识资源的状态,由服务端产生。浏览器会将这串字符串通过 If-None-Match
首部传回服务器,验证资源是否已经修改,如果没有修改,如下图:
If-None-Match
示例:
If-None-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
If-None-Match: W/"67ab43", "54ed21", "7892dd"
If-None-Match: *
ETag
相较于 last-modified
的好处:
- 支持某些无法精确得到资源最后修改时间的服务器;
- 更加精确: 因为
Last-Modified
指定的修改时间是精确到秒级的,如果服务器上的文件在一秒内发生多次更改,单纯依靠文件修改时间就检测不到文件已经发生变化 ; - 有些文件的最后修改时间改变了,但是内容没改变或者修改并不重要,这时使用
ETag
就可以灵活地让资源继续使用旧的缓存文件。
什么时候使用实体标签和最近修改日期
如果服务器回送了一个实体标签,HTTP/1.1
客户端就必须使用实体标签验证器。如果服务器只回送了一个 Last-Modified
值,客户端就可以使用 If-Modified-Since
验证。如果实体标签和最后修改日期都提供了,客户端就会同时使用这两种方法验证,当两个验证都通过时,才会返回 304 Not Modified
。
其他(不常用)条件首部包括:
f-Unmodified-Since
:当在进行部分文件的传输时,获取文件的其余部分之前要先确保文件未发生变化,此时这个首部就是非常有用的。If-Range
:支持对不完整文档缓存If-Match
:用于与 Web 服务器交互时的并发控制
总结
浏览器缓存流程(该图来源于网络):
cache-control
指令使用(该图来源于网络):
应用层缓存
我们常用的存储方式 Cookie
、LocalStorage
、SessionStorage
也可作为应用层的缓存,是可供开发者支配的缓存空间。
Cookie:
- 大小:一般为 4KB
- 每次请求会默认携带在 HTTP 头中,如果使用
Cookie
保存过多数据会降低 HTTP 利用率 - 生命期:一般由服务器生成,可设置失效时间。如果在浏览器端生成
Cookie
,默认是关闭浏览器后失效 - 接口封装较差,需要程序员自己封装,源生的
Cookie
接口不友好
LocalStorage:
- 大小:为 5M 左右
- HTML5 的本地存储,只在本地使用,不与服务端通信
- 生命期:除非主动删除数据,否则数据永远不会过期
- 接口封装较好
SessionStorage:
- 大小:为 5M 左右
- HTML5 的本地存储,只在本地使用,不与服务端通信
- 生命期:会话级别的浏览器存储
- 接口封装较好
LocalStorage
在 PC 上的兼容性不太好,而且当网络速度快、协商缓存响应快时使用 LocalStorage
的速度比不上 304。并且不能缓存 css 文件。而移动端由于网速慢,使用 LocalStorage
要快于 304。
服务端缓存
顺带一提:
服务端缓存最常见是反向代理服务器缓存,利用反向代理服务器,可以尽量地利用缓存下来的资源,减少向源服务器发送请求,从而分担服务器端的访问压力。
一些大型站点会使用CDN
,目的是为了让用户就近访问,加快页面的载入速度,而在每个CDN
节点同样也会缓存用户访问过的资源,因此也属于服务器端缓存的范畴。