场景 1:
每次你打开 XXXXHub, 准备麒麟臂, 满心欢喜的等待一张图片加载回来. 图片终于不负众望加载回来了, 可这居然是昨天看过的那张! 这简直大煞风景! 谁能忍!
场景 2:
有些时候, 办公室里会传来产品大人的尖叫:"怎么网页还是原来的样子?" 然后你会听到前端 GGMM 们不屑的回答:"我都说了, 有缓存". 嗯, 对的, 网页没有改变, 这是 HTTP 缓存在起作用. 因为浏览器在使用同样的资源渲染网页.
HTTP 缓存让前端 GG 们又爱又恨. 虽然 HTTP 缓存在某些瞬间给前端 GGMM 们带来了极大的痛苦(找不到 BUG), 但很大程度上, 缓存也加速了人们锻炼麒麟臂的机会.
合理利用好 HTTP 缓存, 我能就能给每一位网页浏览者 +1s. 下面我们一起来了解一下 HTTP 缓存, 揭开他神秘的面纱.
什么是 HTTP 缓存?
HTTP 缓存 (或 web 缓存) 是用于临时存储(缓存)Web 文档(如 html 页面和图像), 以减少服务器延迟的一种信息技术. HTTP 缓存系统会保存下通过这套系统的文档的副本; 如果满足某些条件, 则可以由缓存满足后续请求. HTTP 缓存系统既可以指设备, 也可以指计算机程序.
简单来说, 就是在打开网页的时候, 将网页中可能重复加载的资源, 按照一定的规则保存到本地, 以便第二次打开同样的网站时, 可以重复利用已有资源.
一个良好的缓存策略, 可以:
降低资源的重复加载
提高网页的整体加载速度
节省用户流量
给前端 GG 带来痛苦(大雾
2 种缓存机制
一般来说, 会有两种缓存机制, 分别是: 协商缓存和强缓存.
假设资源 A 已经躺在了本地某个不知名角落里. 现在, 我们要再拿一次资源 A.
强缓存 -- 浏览器说了算
表现形式:
- 200 OK (from memory cache)
- 200 OK (from disk)
浏览器发现本地资源 A 后, 浏览器根据之前服务器说下的验身规则, 给本地资源 A 验明正身.
如果符合要求, 浏览器会直接返回这个本地资源! 如果不符合要求, 浏览器会想服务端发一个资源请求.
协商缓存 -- 服务器说了算
命中后的表现形式:
304 Not Modified
浏览器发现本地资源 A 后, 浏览器给服务器发了一个请求, 将资源 A 相关的信息告诉服务器, 问问服务器本地的资源 A 还能不能用.
如果服务器说还能用(返回 304), 浏览器会直接返回本地资源 A. 如果不符合要求, 服务器会直接返回一份完整的资源 A.
4 种缓存策略
如果服务器在头部中配置了缓存策略, 那么浏览器就会自动执行对应的逻辑了.
- Expires
- Expires: Wed, 11 May 2018 07:20:00 GMT
这是来自 HTTP 1.0 的头部内容. 他表示的是资源过期时间. 在用户对已缓存资源发起第二次请求的时候, 浏览器会将 Expires 对应的时间和本地时间比对.
P.S.: 请求头部带上 Pragma: no-cache 会让 Expires 失效.
沿用旧资源, 返回 200 OK (from cache) 向服务端发起请求, 要一个新资源
Cache-Control
Cache-Control: public, max-age=604800
来自 HTTP 1.1, 是一个被广泛使用的头部字段. 可以被用在请求头部, 响应头部.
- no-store
- Cache-Control: no-store
禁止使用缓存. 不允许将文件缓存到本地.
- no-cache
- Cache-Control: no-cache
必须先与代理服务器确认是否更改, 然后再决定使用缓存还是请求, 类似于协商缓存(304)
- public
- Cache-Control: public
可以被所有用户缓存(多用户共享), 包括终端和 CDN 等中间代理服务器 一般搭配其他 cache-control 配置一同使用.
- private
- Cache-Control: private
与 public 属性相反, private 意味着: 资源只能被终端浏览器缓存(而且是私有缓存), 不允许中继缓存服务器进行缓存. 一般搭配其他 cache-control 配置一同使用.
- max-age
- Cache-Control: max-age=31536000
告诉浏览器资源多久之内有效. 在这个有效期之内, 浏览器会直接返回已经存在本地的资源. 是一个经典的缓存过期机制.
- s-maxage
- Cache-Control: max-age=3600
覆盖 max-age 或者 Expires 头, 但是仅适用于共享缓存(比如各个代理), 并且私有缓存中它被忽略.
- must-revalidate
- Cache-Control: must-revalidate
revalidate = 使重新生效; 使重新有法律效力. 意味着缓存在考虑使用一个陈旧的资源时, 必须先验证它的状态, 已过期的缓存将不被使用.
ETag && If-None-Match
缓存的强校验器(看文件内容)
- ETag
- ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
- ETag: W/"0815"
来自响应头部. 我们可以把 ETag 理解为资源文件的指纹. 资源变化都会导致 ETag 变化, 跟最后修改时间没有关系, ETag 可以保证每一个资源是唯一的. 没有明确指定生成 ETag 值的方法. 通常, 使用内容的散列, 最后修改时间戳的哈希值, 或简单地使用版本号. 例如, MDN 使用 wiki 内容的十六进制数字的哈希值.
- If-None-Match
- If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
在请求资源的时候, 浏览器会将它放在放在请求头部. 一般来说, 它的值, 其实就是下载资源时响应头部下发的 ETag.
Last-Modified && If-Modified-Since
一个缓存的弱校验器(精确度比 ETag 要低).
- Last-Modified
- Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
一个响应头部. 包含源头服务器认定的资源做出修改的日期及时间. 浏览器会将 Last-Modified 与资源绑定在一起. 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致.
- If-Modified-Since
- If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
一个请求头部. 一般用在 GET 请求. 其实就是下载资源时响应头部下发的 Last-Modified.
服务器收到请求后, 会比较: 当前资源的修改时间和 If-Modified-Since.
当前资源修改时间 <= If-Modified-Since 意味着资源在 If-Modified-Since 这个时间点之后没有被改动, 返回 304 + 空包.
当前资源修改时间> If-Modified-Since 意味着资源在 If-Modified-Since 这个时间点之后改动了, 返回 200 + 完整资源.
主流搭配
Cache-Control: max-age + expires + last-modified
测试题
概念我们已经看完了, 又长又臭. 如果这时候不来点测试题, 那么很快你又会把它忘光了. 下面我为你准备了几道简单的题目, 目的是让你能更好的记住缓存的相关概念, 而且也知道他们是怎么应用的.
1, 如果在 request/response header 中存在: cache-control: max-age=0, 会怎样?
告诉浏览器, 必须发请求重新验证资源. 这时候会走协商缓存机制. 可能返回 200 或者 304
2, 如果在 request/response header 中存在: cache-control: no-cache, 会怎样?
告诉浏览器, 必须发请求重新验证资源. 这时候会走协商缓存机制.
3, 如果在 request/response header 中存在: cache-control: must-revalidate, 会怎样?
告诉浏览器, 必须发请求重新验证资源. 这时候会走协商缓存机制.
4, 如果在 response header 中存在: Cache-Control: max-age=60, must-revalidate, 会怎样?
先来看看 must-revalidate 的定义: 告诉浏览器, 缓存服务器, 本地副本过期前, 可以使用本地副本; 本地副本一旦过期, 必须去源服务器进行有效性校验.
基于上面定义, 可以知道这个头部的执行操作是: 如果资源在 60s 内再次访问, 可以直接返回强缓存; 如果超过 60s, 则必须发送网络请求到服务端, 去验证资源的有效性.
5, 强缓存 + 协商缓存. 强缓存过期, 协商缓存返回 304, 这时候头部里会带上强缓存的配置吗?
看服务端是如何配置的. 这个说不准. 不过一般情况下, 会返回.
6, 为什么大厂都不怎么用 etag?Yahoo 的 YSlow 页面分析工具为什么推荐关闭 ETag?
大型网站多使用负载分担的方式来调度 HTTP 请求, 所以, 同一个客户端对同一个页面的多次请求, 很可能被分配到不同的服务器来相应, 而根据 ETag 的计算原理(计算参数包括 inode, 最后修改时间, 和文件大小), 不同的服务器, 在其它所有方面都一样的情况下, 对于同一页面也能够计算出不同的 ETag. 因此, 这时, 虽然页面没有发生任何变化, 但是服务器还是会给出不同的 ETag, 重新发送完整的页面内容. 这并不是我们希望发生的.
ETag 的计算占用服务器的 CPU 资源.
7,cache-control:max-age + etag + last-modified + expires, 判断顺序是什么?
如果 expires 和 cache-control 同时存在, cache-control 会覆盖 expires.
建议两个都写, cache-control 是 http1.1 的头字段, expires 是 http1.0 的头字 段, 都写兼容会好点.
8,s-maxage 和 max-age 同时配置, 会有什么效果?
max-age 给客户端用, s-maxage 给代理服务器用
s-maxage 一般配置一个比 max-age 要更小一些的值, 避免代理服务器直接使用 max-age, 将文件缓存太久
结语
看完了这又长又臭的文章之后, 相信你对 HTTP 缓存会有一个更深入的了解. 如果你还有什么不懂的话, 欢迎留言, 我会尽我所能解答你的~~
BTW: 冬至到了, 广东的盆友注意防暑降温!
来源: https://juejin.im/post/5c1daddb6fb9a049d37f15c4