缓存的优点
- 减少了不必要的数据传输,节省带宽
- 减少服务器的负担,提升网站性能
- 加快了客户端加载网页的速度
- 用户体验友好
缓存的缺点
如果资源有更改,客户端不及时更新的情况下会导致用户获取信息滞后。
协商缓存相关的 header 字段
协商缓存都是由服务器来确定缓存资源是否可用的,所以客户端与服务器端需要某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问,这主要涉及到下面两组 header 字段。
这两组搭档都是成对出现的,即第一次请求的响应头带上某个字段(Last-Modified 或 Etag),则后续请求则会带上对应的请求字段(If-Modified-Since 或 If-None-Match),若响应头没有Last-Modified或Etag字段,则请求头也不会由对应的字段
-
Last-Modified/If-Modified-Since
二者的值都是 GMT 格式的时间字符串,具体过程- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上Last-Modified的header,这个header表示这个资源在服务器上的最后修改时间
- 浏览器再次跟服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值
- 服务器再次收到资源请求时,根据浏览器传过来的If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回 304 Not Modified,但是不会返回资源内容,如果有变化,就正常返回资源内容,当服务器返回 304 Not Modified 的响应时,response header 中不会再添加 Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回304 的 response header。
- 浏览器收到 304 的响应后,就会从缓存中加载资源
- 如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified 的 Header在重新加载的时候会被更新,下次请求时,If-Modified-Since 会启用上次返回的 Last-Modifed 的值
Etag/If-None-Match
ETag 是 Entity Tag 的缩写,中文译过来就是实体标签的意思。在HTTP1.1协议中其实就是请求HEAD中的一个属性而已。
浏览器请求服务器的某项资源(A)时, 服务器根据A算出一个哈希值(3f80f-1b6-3e1cb03b)并通过 ETag 返回给浏览器,浏览器把"3f80f-1b6-3e1cb03b" 和 A 同时缓存在本地,当下次再次向服务器请求A时,会通过类似 If-None-Match: "3f80f-1b6-3e1cb03b" 的请求头把ETag发送给服务器,服务器再次计算A的哈希值并和浏览器返回的值做比较,如果发现A发生了变化就把A返回给浏览器(200),如果发现A没有变化就给浏览器返回一个304未修改。这样通过控制浏览器端的缓存,可以节省服务器的带宽,因为服务器不需要每次都把全量数据返回给客户端。
在服务器端实现中,很多情况下并不会用哈希来计算ETag,这会严重浪费服务器端资源,很多网站默认是禁用ETag的。有些情况下,可以把ETag退化,比如通过资源的版本或者修改时间来生成ETag。
当ETag和Last-Modified同时存在时,服务器先会检查ETag,然后再检查Last-Modified,最终决定返回304还是200
为何既有last-modified又有Etag
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候,我们并不希望客户端认为这个文件被修改了,而重新 get
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改(比方说 1s 内修改了 N 次),If-Modified-Since能检查到的粒度时 s 级的,这种修改无法判断(或者说 UNIX 记录 MTIME只能精确到秒)
- 某些服务器不能精确得到的文件的最后修改时间。
强缓存
强缓存:直接从本地副本比对读取,不去请求服务器,返回的状态码是 200。
强缓存主要包括 expires 和 cache-control。
- expires
expires 是 HTTP1.0 中定义的缓存字段。当我们请求一个资源,服务器返回时,可以在 Response Headers 中增加 expires 字段表示资源的过期时间。
expires: Thu, 03 Jan 2019 11:43:04 GMT
当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是,有个大问题,发送请求时是使用的客户端时间去对比。一是客户端和服务端时间可能快慢不一致,另一方面是客户端的时间是可以自行修改的(比如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期。
- cache-control
正由于上面说的可能存在的问题,HTTP1.1 新增了 cache-control 字段来解决该问题,所以当 cache-control 和 expires 都存在时,cache-control 优先级更高。该字段是一个时间长度,单位秒,表示该资源过了多少秒后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用该缓存,它不依赖客户端时间。cache-control 主要有 max-age 和 s-maxage、public 和 private、no-cache 和 no-store 等值。
cache-control: public, max-age=3600, s-maxage=3600
- max-age 和 s-maxage
两者是 cache-control 的主要字段,它们是一个数字,表示资源过了多少秒之后变为无效。在浏览器中,max-age 和 s-maxage 都起作用,而且 s-maxage 的优先级高于 max-age。在代理服务器中,只有 s-maxage 起作用。 可以通过设置 max-age 为 0 表示立即过期来向服务器请求资源。
- public 和 private
public 表示该资源可以被所有客户端和代理服务器缓存,而 private 表示该资源仅能客户端缓存。默认值是 private,当设置了 s-maxage 的时候表示允许代理服务器缓存,相当于 public。
- no-cache 和 no-store
no-cache 表示的是不直接询问浏览器缓存情况,而是去向服务器验证当前资源是否更新(即协商缓存)。no-store 则完全不使用缓存策略,不缓存请求或响应的任何内容,直接向服务器请求最新。由于两者都不考虑缓存情况而是直接与服务器交互,所以当 no-cache 和 no-store 存在时会直接忽略 max-age 等。
优先级
优先级:
Cache-Control > Expires > Etag > Last-modify
访问刷新分析
- 标签进入、输入url回车进入
- 按刷新按钮、F5,刷新、网页右键“重新加载”
- ctrl + F5 强制刷新
假设当前有这么一个 index 页面,返回的响应信息如下:
cache-control: max-age=72000
expires: Tue, 20 Nov 2018 20:41:14 GMT
last-modified: Tue, 20 Nov 2018 00:41:14 GMT
- 标签进入、输入url回车进入
由于该例没有设置 no-cache 和 no-store,所以默认先走强缓存路线。根据 cache-control (expires 优先级低)判断缓存是否过期,若没有过期则此时返回 200(from cache)。
若本地缓存已经过期再走协商缓存路线,根据之前的 last-modified 值去与服务器比对,若这个时间之后没有改过则去读取本地缓存,返回 304(not modified)。
否则返回新的资源,状态码 200(ok),并更新返回响应的 last-modified 值。
- 按刷新按钮、F5 刷新、网页右键“重新加载”
这种情况下,实际是浏览器将 cache-control 的 max-age 直接设置成了 0,让缓存立即过期,直接走协商缓存路线。发送的请求头如下:
cache-control: max-age=0
if-modified-since: Tue, 20 Nov 2018 00:41:14 GMT
- Ctrl+ F5 强制刷新
强制刷新的情况下,浏览器会强行设置 no-cache,强制获取最新的资源,就连 if-modified-since 等其他缓存协议字段都会被吃掉。此时发送的请求头如下:
cache-control: no-cache
pragma: no-cache