1. 前言
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。缓存需要合理配置,因为并不是所有资源都是永久不变的:重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期的资源)。
缓存的种类有很多,其大致可归为两类:私有与共享缓存。共享缓存存储的响应能够被多个用户使用。私有缓存只能用于单独用户。本文将主要介绍浏览器与代理缓存,除此之外还有网关缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上的缓存方式,为站点和 web 应用提供更好的稳定性、性能和扩展性。--- 摘自《MDN - HTTP 缓存》
本文主要介绍浏览器缓存(即私有缓存)。在大多数场景下,缓存是最常见的优化手段,简单高效~
缓存的优势特点:
- Client: 重复利用资源,减少网络开销,请求更快地响应解析,更好的用户体验
- Network: 减少重复资源的网络开销,节省带宽,降低成本
- Server: 减小服务器负载,避免过载造成性能瓶颈,简单高效的性能优化手段
2. 缓存机制
常见的 HTTP 缓存只能存储 GET
响应,对于其他类型的响应则无能为力。缓存的关键主要包括request method和目标URI(一般只有GET请求才会被缓存),流程图如下:
2.1 强缓存
强缓存:如果命中强缓存,浏览器发送的请求不会到达服务器,而是直接从浏览器缓存中读取数据,在chrome network中size显示为 disk cache 或者 memory cache,HTTP 状态码为200。
强缓存的字段:Expires
(HTTP/1.0)、Cache-Control
(HTTP/1.0)、Pragma
(HTTP/1.0,效果同Cache-Control: no-cache,主要为了兼容,不做赘述)
2.1.1 Expires
是一个绝对的时间,指定资源到期时间是服务器的具体时间点,该字段存在于服务器响应头, 即在此时间之前,直接读取浏览器缓存数据。如果值为 0, 代表着过去的日期,即该资源已经过期。
Expires
的优点:
-
HTTP/1.0 的产物,向后兼容
-
绝对时间标示失效时间,方便对比
Expires
的缺点:
- 由于是绝对时间,客户端可以通过修改本地时间的方式,使缓存失效
- 时间是由服务端发送的,可能存在服务器时间与客户端时间不一致
- 在缓存未过期之前,客户端是无法得知资源是否修改
2.1.2 Cache-Control
已知Expires
的缺点之后,在HTTP/1.1中,新增了一个字段Cache-control
,该字段表示资源缓存的最大有效时间(相对时间),在该时间内,客户端不需要向服务器发送请求。
Cache-control
常用值如下:
常用值 | 字段说明 |
---|---|
private | 默认值,所有内容只有客户端才可以缓存,代理服务器不能缓存 |
public | 所有的内容都可以被缓存 (包括客户端和代理服务器, 如 CDN) |
max-age | 表示资源能够被缓存(保持新鲜)的最大时间,单位为s |
must-revalidate | 如果超过了max-age的时间,浏览器必须向服务端发送请求,验证资源是否还有效 |
no-cache | 不使用强缓存,浏览器在使用缓存数据时,需要与服务器验证缓存是否新鲜 |
no-store | 真正意义上的“不要缓存”,所有内容都不走缓存,包括强制和对比 |
s-maxage | 设置共享缓存,比如CDN。会覆盖max-age和Expires |
max-stale | 能容忍的最大过期时间,表示客户端愿意接收一个已经过期的资源 |
min-fresh | 能够容忍的最小新鲜度。标示了客户端不愿意接受新鲜度不多于当前的age加上min-fresh设定的时间之和的响应 |
Cache-Control
的优点:
- HTTP/1.1 的产物,解决Expires服务器与客户端相对时间不一致的问题
- 可以组合使用指令,达到多个目的
Cache-Control
的缺点:
-
在HTTP/1.0中,无法使用
-
在缓存未过期之前,客户端是无法得知资源是否修改
2.2 协商缓存
当强缓存失效or没设置时,由客户端与服务器通过某种标识来判断(协商)资源是否可用,所以字段是成对出现的。请求头If-Modified-Since
和响应头Last-Modified
配对,请求头If-None-Match
和响应头ETag
配对。如果命中,HTTP 状态码为304。
2.2.1 Last-Modified和If-Modified-Since
是一个绝对的时间 ,表示服务器的资源最后一次修改的时间,在请求头中将上次的 Last-Modified
的值写入到请求头的 If-Modified-Since
字段,对比该次请求响应头Last-Modified
与If-Modified-Since
时间,若时间一致,返回304状态码。
Last-Modified
和If-Modified-Since
优点:
-
HTTP/1.0 的产物,向后兼容
-
由于每次都经过服务器检验,不存在客户端无法得知资源是否修改的问题
Last-Modified
和If-Modified-Since
缺点:
- 如果服务器资源更新在1S以内,可能无法识别1S内多次修改的情况
- 如果请求的是动态资源,无法起到缓存作用。例如:SSR返回的页面
2.2.2 Etag和If-None-Match
为了解决Last-Modified
和If-Modified-Since
的缺点 ,在HTTP/1.1 新增了Etag
和If-None-Match
字段,存储的是文件的特殊标识(一般由Hash生成),例如 Nuxt.js
服务端渲染页面时自带 Etag
。 同样在请求头中将上次的 Etag
的值写入到请求头的 If-None-Match
字段,对比该次请求响应头Etag
与If-None-Match
值,若一致,返回304状态码。
Etag
和If-None-Match
优点:
-
在HTTP/1.0中,无法使用
-
由于每次都经过服务器检验,不存在客户端无法得知资源是否修改的问题
-
可以更加精确的表示资源内容是否修改,可以识别1S内多次修改内容的情况
Etag
和If-None-Match
缺点:
- 计算
Etag
值会带来额外的性能损耗 - 如果集群中服务器算法不一致,会导致
Etag
对比失效,无法起到协商缓存的作用
3. 用户行为对浏览器缓存的影响
用户操作 | Expires/Cache-Control | Last-Modied/Etag |
---|---|---|
地址栏回车 | √ | √ |
页面链接跳转 | √ | √ |
新开窗口 | √ | √ |
前进、回退 | √ | √ |
F5刷新 | X | √ |
Ctrl+F5强制刷新 | X | X |
5. Vary
Vary
HTTP 响应头决定了对于后续的请求头,如何判断是请求一个新的资源还是使用缓存的文件。当缓存服务器收到一个请求,只有当前的请求和原始(缓存)的请求头跟缓存的响应头里的Vary都匹配,才能使用缓存的响应。
简而言之,可以通过Vary
字段来通知缓存哪些请求头字段用于区分相同的URL请求,服务端存在不同内容的响应。
6. CDN(公有缓存)
在大多数前端项目中,静态资源都会使用CDN加速用户访问。CDN
缓存策略因为服务商的不同而不同,但一般会根据源站响应头中的Cache-Control
和 Expires
字段来设置节点缓存时间。因此,CDN缓存返回的Cache-Control
和 Expires
一般源自服务器或者Nginx
配置。
7. 总结
- 优先级:强缓存高于协商缓存,
Cache-Control
高于Expires
,Etag
高于Last-modified
- 可以通过状态码来区分其为两大类,协商缓存为304和强缓存为200
- 如果服务器输出了
ETag
,没有必要再输出Last-Modified
,Etag
更能精准标识资源的修改情况 Etag
不是自带的,有些框架会默认添加,例如 nuxt.js 。nodejs
可以使用 weak tag