-
缓存过程
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
-
缓存位置
优先级从上至下
-
Service worker
- Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能,传输协议必须为 HTTPS,因为 Service Worker 中涉及到请求拦截。 Service Worker实现缓存功能一般分为三个步骤:
-
- 注册 Service Worker
- 安装 Service Worker
- oninstall 事件的处理程序执行完毕后激活
在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。当 Service Worker 没有命中缓存的时候,会去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
-
Memory cache
内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。
-
【常用】Disk cache
硬盘中的缓存,虽然读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。
-
Push cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。 HTTP/2 push is tougher than I thought
-
缓存策略
-
强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control,同时存在时 Cache-Control > Expires ( Expires 已经逐渐废弃,考虑兼容会加上)。
Cache-Control
多个指令以逗号分隔,缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。
key | 描述 | 作用 | |
---|---|---|---|
请求指令 | max-age= | 能接受的最大有效时间 | 到期时间 |
max-stale[=] | 客户端愿意接收一个已经过期的资源,能接受的最大过期时间 | 到期时间 | |
min-fresh= | 能够容忍的最小新鲜度 | 到期时间 | |
no-cache | 强制协商缓存 max-age=0,must-revalidate | 可缓存性 | |
no-store | 不使用任何缓存 | 可缓存性 | |
no-transform | 不得对资源进行转换或转变 | 其他 | |
only-if-cached | 强缓存失败后不协商 | 其他 | |
响应指令 | must-revalidate | 一旦资源过期,强制协商缓存 | 重新验证/重新加载 |
proxy-revalidate | 同上,针对代理服务器 | 重新验证/重新加载 | |
no-cache | 强制协商缓存 max-age=0,must-revalidate | 可缓存性 | |
no-store | 不使用任何缓存 | 可缓存性 | |
public | 客户端和代理可缓存,响应可被任何中间节点缓存 | 可缓存性 | |
private | 仅客户端可缓存 | 可缓存性 | |
max-age= | 资源的有效期 | 到期时间 | |
s-maxage= | 覆盖max-age 或者Expires 头,但是仅适用于共享缓存(代理) | 到期时间 | |
no-transform | 不得对资源进行转换或转变 |
Expires
是 HTTP/1 的产物,用来指定资源到期的时间,是服务器端的具体的时间点。Expires=max-age + 请求时间,
-
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag。主要有以下两种情况:
- 协商缓存生效,返回304和Not Modified
- 协商缓存失效,返回200和请求结果
Etag
表示请求资源在服务器的唯一标识,Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。
Last-Modified
用于标记请求资源在服务器上的最后一次修改时间。 浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。
弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
- 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
-
缓存的实际应用
-
频繁变动的资源
Cache-Control: no-cache
对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
-
不常变化的资源
Cache-Control: max-age=31536000
通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。 在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。
-
tradeoff
-
用户行为对缓存的影响
- 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
- 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
- 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有
Cache-control: no-cache
(为了兼容,还带了Pragma: no-cache
),服务器直接返回 200 和最新内容。