LiuL0703 / blog

Home Page:https://liul0703.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

浏览器的缓存策略

LiuL0703 opened this issue · comments

说到缓存其实分很多种,比如CDN缓存,代理服务器缓存,数据库缓存,浏览器缓存等等。这里我们主要说浏览器的缓存。其中重点部分是HTTP的缓存策略。当一个请求发出后,浏览器接收到响应,需要根据响应头里的缓存策略来决定是否使用以及如何使用缓存。我们以Chrome为例,Chrome在很多抽象层面都实现了缓存,通常可以大致分为三类:

  • HTTP cache
  • Service Worker Caches
  • Blink cache

本文主要介绍HTTP缓存部分。关于HTTP缓存最为熟知和关键,通过网络发出的每个请求都严格遵循RFC的HTTP规范标准,其中直接决定HTTP缓存的几个规则是Cache-Control、ETag、Last-Modified、Expires

HTTP Cache

Cache-Control

Cache-Control的可取值有如下两种类型值,需要注意的一点是request请求头里带的值并不代表最终响应值。最终值仍需要服务端决定,请求头中的值只会作为一个参考,所以我们会重点关注响应头字段的内容。

Cache-Control : cache-request-directive | cache-response-directive

cache-request-directiveno-cache | no-store | max-age | max-stale | min-fresh | no-transform | only-if-cached

cache-response-directivepublic| private | no-cache | no-store | no-transform | must-revalidate | proxy-revalidate | max-age | s-maxage

Public vs Private

public表示可以任意缓存,可被作为共享缓存使用。即可以再多个用户之间共享。private则表示只针对特定用户做缓存并且不可作为共享缓存。MSDN中描述 Cache-Control 的默认值为private。

如图表示此响应只可作为私有缓存,而且每次请求都需要服务器验证其有效性。

Snipaste_2019-11-02_14-02-58-7e950c4f-4c8b-47a4-bfab-d9976747e7e1

must-revalidate vs proxy-revalidate vs no-cache vs no-store

must-revalidate表示缓存过期前浏览器、缓存服务器可以正常使用,一旦缓存过期则必须去源服务器验证响应数据的有效性。

proxy-revalidate类似于must-revalidate,区别是其仅针对共享缓存。

no-cache表示浏览器或缓存服务器可做缓存,但不管缓存是否过期,使用前必须经过源服务器验证其缓存的有效性。no-cache可以看做是must-revalidate的定制版,不同点是must-revalidate是在请求过期后直接从服务器获取响应,而no-cache则默认缓存是立即过期的。举个例子来说:如果某个响应缓存的时间是10秒有效,则在这个时间范围内must-revalidate会在10秒后使用缓存时被触发,而no-cache则是0秒触发must-revalidate。

no-store表示绝对🚫禁止缓存响应,每次必须向源服务器请求新的资源。

如图所以:此站点的这个请求通过添加no-cache no-store must-revalidate以及pragm:no-cache(用于兼容HTTP 1.0)来禁止缓存,每次请求都要发往源服务器。

Snipaste_2019-11-02_13-45-12-bd6c08d4-ad99-4b37-b020-098928e56309

再看一个例子,所有的Chrome extension采用的策略是no-cache,即缓存服务器在返回备份响应前需要向源服务器校验其有效性,如果可用则返回备份,否则要向源服务器发起请求。

Snipaste_2019-11-02_14-08-18-d53e287d-d974-43e5-8873-031353ac2e0e

max-age vs s-maxage

max-age用来表示表示缓存的有效时间。单位是秒(s)。超出这个时间段后被认为过期。那么在这个max-age时间段内再次发出的请求,浏览器会直接使用缓存,不会再向浏览器发出请求。另外如果max-age和Expires同时存在时,会覆盖掉Expires字段(限于HTTP 1.1及之后版本)。

s-maxage只用来表示共享缓存(比如CDN缓存)的有效时间,作用类似max-age单位同样是秒(s),即max-age适用于普通缓存,s-maxage适用于共享缓存。如果response header里同时存在max-age和Expires(后文有提到)则会被s-maxage覆盖,需要注意的是如果缓存是private类型 则s-maxage会被直接忽略。

如下的例子中此缓存的有效期是一分钟。

Snipaste_2019-11-02_13-56-50-ed4aef68-bfd3-4be1-aa13-cf99d54a91e9

no-transform

no-transform表示不得对资源进行任何变更,不能修改Content-Encoding,Content-Range和Content-Type的请求头。用到情况很少,不做过多说明。

Cache-Control除了上述这些属于规范的取值外还有一种可取的范围值类型 cache-extension ,它不属于文档规范的一部分,所以有兼容性问题。这类值中比较典型的代表如:immutable、stale-while-revalidate=、stale-if-error=等。


immutable vs stale-while-revalidate vs stale-if-error

immutable用来描述响应内容不会随时间变化。因为资源(如果未过期)在服务器上不变,所以即使在用户刷新页面时,也不需要再重新验证其有效性(例如,If-None-Match或If-Modified-Since)

stale-while-revalidate表示客户端在指定时间内允许在异步检查校验新的响应时,可接收已过期的资源响应。例如:Cache-Control : max-age=600, stale-while-revalidate=30 上述规则表示这条资源的有效期的600秒,若在异步状态下检查新的响应时过了有效期但依然可以最多保持30秒可用。如果验证未通过或者没有被触发,则在30秒后stale-while-revalidate规则不在有效,缓存真正过期。

state-if-error用来表示如果客户端获取最新资源失败在指定的时间内可接受已过期的资源响应。例如:Cache-Control: max-age=600, stale-if-error=1200它表示资源的有效期为600秒如果在过期后向服务器请求遇到错误(比如服务器返回500、502、503、504等)则额外增加1200秒有效期。如果在1800秒后去请求则缓存过期直接返回错误信息。

如图是一个简略版的Cache-Control字段的判断步骤

Expires

Expires用来指定缓存的过期时间,是一个绝对时间,由服务端生成。Expires字段告诉浏览器在响应过期前的时间内无需再次请求服务器,可以直接使用缓存值,除非强制刷新或者缓存被清空。同时还有个缺陷在于服务器时间和用户端时间可能存在不一致,所以HTTP 1.1加入Cache-Control来改进这个问题。Expires的时间其实就是请求时的时间➕max-age。上文我们提到如果存在Cache-Control字段并且值为max-age则Expires字段就会被覆盖,也就意味着Cache-Control的优先级要高于Expires。Expires字段一般还要搭配Last-Modified使用。

ETag

想象这么一个场景:一个请求的缓存资源有效期是120秒,过了这个时间点因为缓存已经过了有效期,所以浏览器不能使用。当一个新的请求发出后,浏览器需要重新获取整个响应资源,但是如果响应内容未发生变化的话,这样是很浪费资源的。ETag就是来解决这个问题的。

ETag是由实体内容生成的一段hash值,由服务端生成,于If-None-Match搭配使用,当客户端再次请求时通过If-None-Match带上ETag的值向发送给服务端,服务端通过对比ETag值是否相等来验证资源是否发生变化。如果未变化则返回304和一个空响应,从而更高效的利用缓存节省资源消耗。

Snipaste_2019-11-05_13-53-19-c0dfd14b-81b0-4070-a403-6ff8a2f3608e

如图所示:因为其资源未发生变化,所以服务器返回304和空响应。

Snipaste_2019-11-05_13-53-19-c0dfd14b-81b0-4070-a403-6ff8a2f3608e

Last-Modified

用来表示响应文件的最后修改时间,可以用来检查资源是否更新的一种方式。当客户端再次向服务端请求时,会向服务器发送带If-Modified-Since字段来判断资源在Last-Modified时间点后是否被修改过。如果没有被修改则返回304和空响应,反之重新向服务器获取最新资源。当然说到这里会发现Last-Modified和ETag字段作用相同,但是它们还是细微的差别的。HTTP1.1中ETag的出现主要是为了解决Last-Modified的一些不足。

  1. 某些文件可能是定期生成的但是内容没变,Last-Modified值却变了,就导致缓存失效。
  2. Last-Modified的时间描述只能精确到秒级别,如果响应资源的变化在秒以内,就无能为力了。
  3. 有些服务器无法准确判断页面的最后修改日期。

Last-Modified与If-Modified-Since配合使用,当响应头带上Last-Modified,那么下次再请求时会把这个值加到请求头的If-Modified-Since中,服务器通过对比这个值来判断资源是否发生变化,如果没有发生变化则返回304和空响应。

Pragma

用于兼容HTTP 1.0,因为HTTP 1.0不支持Cache-Control,当Cache-Control可用时会被忽略。主要用来做Cache-Control:no-cache的备胎。它只有一个可取值即:no-cache。使用的唯一场景就是向后兼容HTTP 1.0。

上面四种缓存规则的优先级为:Cache-Control > Expires > ETag > Last-Modified

小知识:浏览器中我们经常会看到Memory Cache 和Disk Cache那么来看下它们之间有什么区别?实际上Memory Cache是将资源存储在RAM里,所以访问速度会很快,但是当你关闭浏览器后缓存立马失效,而Disk Cache则是将缓存资源写在磁盘里,读取资源时从磁盘中加载读取,有效期更持久。

其他

浏览器的其他缓存策略比如HTML的Meta标签,Manifest,LocalStorage SessionStorage等

Manifest

Manifest已经被移除出标准规范所以不在推荐使用,建议使用Service Workers。

LocalStorage SessionStorage

关于LocalStorage和SessionStorage可以看之前总结的这篇文章Cookie, LocalStorage 与 SessionStorage

参考链接: