HTTP的缓存机制以及原理

477 阅读7分钟

前端缓存可以分为浏览器缓存 和 HTTP 缓存。(浏览器缓存就是浏览器本地存储的方式)

场景:

第一遍打开淘宝

刷新

从👆图中我们可以看到在size的位置有👇几个词的出现

  1. ServiceWorker
  2. memory Cache
  3. disk Cache
  4. 523k
他们的优先级是从上到下寻找, 找到就返回, 找不到则继续

那么这些词都是什么意思呢❓ 这就是缓存来自的位置!!下面一起来看看有关缓存的知识

一. 为什么需要缓存呢?

前端页面有些东西是重复的不变的,所以把他们放在缓存区里,如果命中直接去缓存区拿,性能上会得到大大的提升,是一种很好的web优化手段

二. 缓存都有几种?

首先来看个图, 然后看图详解

从上图中可以看出,按照位置划分的话,缓存一共分为3种,这里我们主要介绍第二种HTTP 缓存。

1.HTTP cache

disk缓存由HTTP请求字段控制: 通过请求头,响应头的字段指定缓存规则

HTTP缓存的好处:

  • 减少冗余的数据传输,节省了网费
  • 缓解了服务器的压力,大大提高了网站的性能
  • 加快了客户端加载网页的速度

1.1强制缓存:减少请求数量

优先级高,如果这个生效,则不进行对比缓存。如果命中缓存就直接去缓存数据库取,不需要请求服务器减少请求数量,如果考虑优化网页性能,应首先被考虑

借用网上的一张图片

请求数据
if(命中缓存 && 没有过期){
    缓存数据库 返回数据
}else{
    1.没有命中缓存
    2.向服务器请求数据.then(res=>{
        3.拿到数据+ 缓存规则
    })
    4.将缓存规则 + 数据 存在缓存系统中
}

Expries (HTTP1.0+ 字段):是个绝对时间(GMT)

服务器返回的到期的有效时间。

缺点:因为缓存时间是服务端生成的,可能会和客户端有误差。

Cache-Control:(HTTP1.1字段):是个相对时间

现在浏览器默认使用都是HTTP1.1

Cache-Control是个相对时间值可以混用, 常用值有:

  • private:私有缓存只能用于单独的用户(默认值),不能被代理服务器缓存
  • public:共享缓存可以被多个用户使用
  • no-cache:不适用强制缓存,走对比缓存
  • max-age:缓存的过期时间
  • no-store:一切都不缓存(Firfox需要cache-control:no-cache; no-store;)
  • must-revalidata : 指定如果页面是过期的,则去服务器进行获取。这个指令并不常用,就不做过多的讨论了。

为了兼容可以Expries和Cache-Control都设置

注意:在HTTP1.0中,如果想使用no-cache,通常要使用pragma:no-cache(也是唯一值),但是这个字段只是浏览器约定俗成的实现,没有确切的规范,因为缺乏可靠性,在当前网络环境下其实用处已经很小了,pragma优先级高于cache-control,但是HTTP1.1 已经废弃了这个字段。

1.2对比缓存(协商缓存):响应体积节省

当强制缓存到期了,就需要使用对比缓存了

大概意思就是浏览器先请求缓存数据库,要一个标识,
拿着标识去找服务器确认,
服务器去对比last-modified||Etag字段判断是不是有更新
没有更新就304,有的话就200,
然后再把新的规则+数据带给客户端,
客户端在把数据和规则写进缓存服务器
【304只是个状态码,并没有实际的文件内容,因为体积节省了,从而得到了优化】

1. 向缓存服务器发请求
2. 缓存数据返回标识(比如Etag='2020')
3. 浏览器把Etag的值赋值在请求头IF-None-Match中,然后传给服务端 此时IF-None-Match='2020'
if(IF-None-Match == Etag){  //未修改
    获取缓存数据显示304
}else{
    返回数据 + 缓存规则(比如Etag='2030') 返回200 
    客户端将数据 + 缓存规则(Etag='2030')存入 缓存系统
}

借用

Last-Modified & If-Modified-Since:

服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间

缺点:
  • 最后一次的时间可能获取的不准确
  • 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
  • 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。

Etag & If-None-Match

为了解决上述问题,出现了一组新的字段 Etag 和 If-None-Match.

  • Etag 存储的是文件的特殊标识(一般都是 hash 生成的)
  • 之后的流程和 Last-Modified 一致,只是字段不同
  • 服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
  • Etag 的优先级高于 Last-Modified

2.memory Cache:

黑盒:是浏览器为了加快读取速度而进行的自身的优化行为,不受开发者,也不受请求头的控制

  • 几乎所有的网络请求都会被自动加入到Memory Cache中
  • 正常情况下关闭tab就会清除,极端情况内存占得太多,前面的也会清除
  • 一个页面有2个相同的请求,只请求一次,避免浪费
  • 在从MC读缓存的时候,浏览器会忽略max-age和nocache等头设置,因为它是短期的存储
  • no-store 可以使一切都不缓存

3.ServiceWorker:

由开发者控制:SW缓存较对其他缓存更灵活,他可以选择缓存哪些文件,什么情况下缓存,取出哪些缓存

我们可以从F12 Application->Cache Storage 找到它。
这个缓存是永久的,及时关闭了tab或者浏览器,下次打开依然在。
如果没有命中缓存,会fetch()方法继续获取资源。
这时候浏览器会去memory和disk中进行下一次找缓存的工作了
如果要清除,手动调用Api cache delete。或者超容量了被浏览器清除了

经过ServiceWorker的fetch()获取的资源,即便他没有命中SW缓存,甚至走了网络请求,也会被标记成from SW

三、看完知识点来看下面的具体例子吧

eg0:内存缓存 eg1:强制缓存 eg2:对比缓存


四、总结:

缓存大致分为三种

  • memory cache: 不受开发者控制,是浏览器自发的行为,关闭tab就清除了
  • disk磁盘缓存也就是HTTP缓存,顾名思义受HTTP头的控制。分为2种:
    • 强缓存(级别更高):减少请求数量
      • 有缓存的话:不需要与服务器交互,直接从缓存数据库里拿
      • 失效情况:
        1. expires,max-age 过期了,则走对比缓存
        2. cache-control 设置为 no-cache, 则走对比缓存
    • 对比缓存(协商缓存):无论怎么都与服务端交互,但是304时体积会减小
      • last-modified / if-modified-since (http1.0)
      • Etag / If-None-Match (HTTP1.1):级别更高
  • servciceWorker是开发者开发的脚本,可控制,更灵活

浏览器的强制刷新其实就是发动的请求头都带有no-cache.

强制缓存,命中缓存的时候会直接从缓存数据库拿数据,所以会减少请求数量,返回的状态码是200。当expires 或者cache-control 的max-age 失效,或者设置了no-cache 时,会走对比缓存

而对比缓存一定会与服务器交互,第一次请求后,服务端会返回一个最后修改时间,第二次请求时,浏览器会在请求头里带上这个字段,让服务端去对比,如果资源发生改变,则返回数据和新的缓存规则,状态码200。如果发现没有修改,则返回状态码304但是不返回数据内容,直接使用缓存数据库里的数据。所以他会节省响应体积,对比缓存的2对字段可以都写上,但是Etag的级别更高

对比缓存2字段的区别就是Etag是HTTP1.1,另外他可以解决Last-modified 存在的一些不准确的问题

推荐这个文章写得很好