缓存机制
前端发起请求到请求结束中间的所有缓存机制:
- DNS 缓存
- CDN 缓存
- 浏览器缓存
缓存可以说是性能优化中简单高效的一种优化方式了,它可以显著减少网络传输所带来的损耗。
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
接下来的内容中我们将通过以下几个部分来探讨浏览器缓存机制:
- 缓存位置
- 缓存策略
- 实际场景应用缓存策略
缓存位置
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 网络请求
Service Worker
Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。
Memory Cache
Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。
Disk Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。
Push Cache
Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。
- 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好
- 可以推送
no-cache和no-store的资源 - 一旦连接被关闭,Push Cache 就被释放
- 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存
- Push Cache 中的缓存只能被使用一次
- 浏览器可以拒绝接受已经存在的资源推送
- 你可以给其他域名推送资源
网络请求
如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。事实上,为了性能上的考虑,大部分的接口都应该选择好缓存策略。
浏览器缓存的优点:
- 减少了冗余的数据传输
- 减少了服务器的负担,大大提升了网站的性能
- 加快了客户端加载网页的速度
缓存策略
通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。
强缓存
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要向服务器发送请求,直接从缓存中读取,state code 为 200。
当我们第一次进入页面,请求服务器,然后服务器进行应答,浏览器会根据response Header来判断是否对资源进行缓存,如果响应头中有expires、pragma或者cache-control字段,代表这是强缓存,浏览器就会把资源缓存在memory cache 或 disk cache中。
第二次请求时,浏览器判断请求参数,如果符合强缓存条件就直接返回状态码200,从本地缓存中拿数据。否则把响应参数存在request header请求头中向服务器请求,看是否符合协商缓存,符合则返回状态码304,不符合则服务器会返回全新资源。
Expires
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Expires 是 HTTP/1 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-control
默认值是no-cache
Cache-control: max-age=30
Cache-Control 出现于 HTTP/1.1,优先级高于 Expires 。该属性值表示资源会在 30 秒后过期,需要再次请求。
Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令,常用指令:max-age,no-cache,no-store,public,private。
Expires与Cache-control的对比
| Expires | Cache-control | |
|---|---|---|
| HTTP版本 | HTTP/1 | HTTP/1.1 |
| 优先级 | 低 | 高 |
| 时间类型 | 绝对时间 | 相对时间(单位秒) |
| 缺点 | 修改本地时间,缓存可能失效;服务器与客户端时间偏差较大时,会导致缓存混乱 |
协商缓存
协商缓存就是强缓存失效后,浏览器携带缓存标识向服务器发送请求,由服务器根据缓存标识来决定是否使用缓存的过程。协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag。
当浏览器发起请求验证资源时,如果资源没有被改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。
Last-Modified 和 If-Modified-Since
Last-Modified 是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。
If-Modified-Since 则是客户端再次发起该请求时,携带上次请求返回的 Last-Modified 值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有 If-Modified-Since 字段,则会根据 If-Modified-Since 的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于 If-Modified-Since 的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。
但是 Last-Modified 存在一些弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成
Last-Modified被修改,服务端不能命中缓存导致发送相同的资源 - 因为
Last-Modified只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
因为以上这些弊端,所以在 HTTP / 1.1 出现了 ETag 。
ETag 和 If-None-Match
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。
If-None-Match 是客户端再次发起该请求时,携带上次请求返回的唯一标识 Etag 值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有 If-None-Match,则会根据 If-None-Match 的字段值与该资源在服务器的 Etag 值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。
Last-Modified与ETag的对比
| Last-Modified | ETag | |
|---|---|---|
| HTTP版本 | HTTP/1 | HTTP/1.1 |
| 优先级 | 低 | 高 |
| 值 | 时间标识该资源的最后修改时间 | 唯一的标识 |
| 服务器响应(命中缓存) | 不会返回 Last-Modify | 将ETag 返回(即使不变) |
| 缺点 | 1. 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源;2. 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源 |
Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。
如果什么缓存策略都没设置,那么浏览器会怎么处理?
对于这种情况,浏览器通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
应用
频繁变动的资源
对于频繁变动的资源,首先需要使用 Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。比如 HTML,可以使用协商缓存。
代码文件
这里特指除了 HTML 外的代码文件,因为 HTML 文件一般不缓存或者缓存时间很短。比如 css、js、图片 使用强缓存,具体实现时可以给文件名带上hash值。
一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行hash处理,只有当代码修改后才会生成新的文件名。基于此,我们就可以给代码文件设置缓存有效期一年 Cache-Control: max-age=31536000,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。
刷新对缓存的影响
-
当ctrl+f5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存。
-
当f5刷新网页时,跳过强缓存,但是会检查协商缓存。
-
浏览器地址栏中写入URL(会走强缓存和/或协商缓存)回车 浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿。(最快)