http缓存你知道多少?

276 阅读12分钟

浏览器缓存规则

什么是浏览器缓存?

每一个浏览器都实现了http缓存,在浏览器与服务器进行http请求的时候,浏览器会将一些文件存放在内存中(memory cache)或者磁盘(disk cache)中,当再次访问该网站时,就可以直接使用缓存中的资源,优化用户体验。

浏览器缓存的优点

  • 减轻服务端压力。不用每次都去读取磁盘
  • 优化用户体验。网页加载时间明显变短
  • 节省流量,减少带宽消耗。

浏览器缓存的演变过程

现存的浏览器缓存有两种方式:

  • 强缓存(expires、cache-control、pragma字段)
  • 协商缓存(last-modified/if-modifed-since、etag/if-None-Match字段)

我们过一遍浏览器缓存的整个发展历程,自然就记住了这两种缓存的使用方式。

某个请求举例:

请求头大小:1KB
响应头大小:1KB
响应内容大小:10KB
完整请求:12KB
  1. 没有浏览器缓存之前

在没有缓存之前,请求每次都会发给服务器,服务器每次都会去查一遍磁盘,拿出请求内容返回给客户端。

请求10遍,花费流量10*12KB = 120KB,时间上也是10倍的请求往返时间。

所以,流量和时间都与请求次数有关。

缺点:
- 浪费用户流量
- 服务器压力很大
- 用户体验度很差
  1. 引入浏览器缓存

过程:浏览器第一次请求 a.js,缓存 a.js 到本地磁盘。(1+10+1 =12KB) 浏览器再次请求 a.js,直接走浏览器缓存(200,from cache),不再向服务器发起请求。(0KB) ...

N次请求:12KB

优点:
- 节省用户流量
- 减轻服务器压力
- 用户体验度非常棒
缺点:
- 浏览器无法获知服务器资源的变化时间,一直使用过期缓存
  1. 服务器告诉浏览器资源过期时间(Expires)

第一次请求

浏览器发出请求(1KB)

服务器将资源、资源的过期时间Expires都返回给浏览器并存储在浏览器磁盘内(10KB+1KB)

第二次请求

浏览器再次请求时查看Expires字段,发现资源没有过期,直接使用缓存(0KB)。

浏览器再次请求时,发现资源已经过期,向服务端重新发请求(12KB),得到新的资源。

N次请求:与次数无关

优点:
在过期时间之内,为用户省了许多流量,减轻了服务端的压力
在过期时间之外,客户端可获得新的服务端资源以及下次资源的过期时间
缺点:
过期之后,不管服务器资源有没有真正变化,都会重新请求
总结:上述过程其实就是http1.0的强制缓存过程,意为在该时间之内,强制使用缓存中的内容。
  1. 服务器告诉浏览器资源上次的修改时间(Last-Modified)

为了解决强制缓存的问题,服务器返回Last-Modified字段,表示资源最后一次修改的时间。

第一次请求

浏览器发出请求(1KB)。 服务器将资源、资源的过期时间Expires、资源最后一次修改时间Last-Modified都返回给浏览器(10KB+1KB)

第二次请求

浏览器再次请求时,发现资源还没过期,直接使用缓存(0KB)

浏览器再次请求时,发现资源已经过期,向服务端重新发请求的时候携带If-Modified-Since字段(与Last-Modified相等),服务端对比资源最后一次的修改时间和客户端返回的该字段。

如果一致的话,就返回304告知客户端资源没变,使用缓存吧(2KB)。

如果不一致的话,就返回200告知客户端资源已变,并同时返回新的资源内容和相应的Last-Modified字段、Expires字段(12KB)。

循环以上过程...

优点:
在过期时间之内,为用户省了许多流量,减轻了服务端的压力
在过期时间之内,服务器检测如果文件没变化,不再把a.js发给浏览器,省去了 10KB 的流量。
在过期时间之内,服务器检测如果文件有变化,则把最新的 a.js 发给浏览器,浏览器能够得到最新的 a.js
缺点:
Last-Modified只能精确到秒,如果一个文件在一秒内发生多次变化,则无法捕捉。
当资源被修改,但是资源内容没有被修改的时候,仍会重新请求服务器资源。
(举例:文件被修改之后,又被改回去)
总结:上述过程其实就是http1.0的协商缓存过程,意为强制缓存过期后,客户端与服务端协商之后,服务端告知客户端是否要用本地缓存。
http1.0缓存的缺点:
Expires用的是绝对时间,客户端时间可被用户调节,所以缓存时间不准确
Last-Modified只能精确到秒
服务器资源被修改但是内容没有发生改变,仍然会重新发送请求。
  1. 服务器增加相对时间(Cache-control)控制强制缓存

Expires字段的缓存时间不准确的问题是由绝对时间引起的,因此http1.1引用了相对时间,服务器告知浏览器在多少秒之后,该资源就会过期,在此时间之内不要再来打扰我。

Cache-control:max-age=100

关于Cache-control字段的其他值在下文中会讲到。

Cache-control、Expires可同时存在,http1.1的字段优先级高于http1.0,即如果有Cache-control字段,则忽略Expires。

优点:
解决了http1.0强制缓存--Expires字段时间不准确的问题
缺点:
仍然具有强制缓存的缺点--过期之后,服务器内容没有发生变化仍然会重新请求
  1. 服务器增加(Etag/If-None-Match)监听文件内容变化

Cache-control字段解决了1.0中强制缓存的问题,但是对于1.0中协商缓存Last-Modified的问题仍然没有解决,所以http1.1引入了Etag/if-None-Match。

第一次请求

浏览器发出请求(1KB)。

服务器将资源、资源的过期绝对时间Expires、相对时间Cache-control(max-age=10)、时间资源最后一次修改时间Last-Modified、表示资源内容唯一性的编号Etag都返回给浏览器(10KB+1KB)

浏览器将这些字段和资源内容存储在磁盘或者内存当中。

第二次请求

10秒之内,浏览器再次请求时,发现资源还没过期(Cache-control优先级大于Expires),直接使用缓存(0KB)

10秒之后,浏览器再次请求时,发现资源已经过期,向服务端重新发请求的时候携带If-Modified-Since字段(与Last-Modified相等)、If-None-Match字段(与Etag相等,优先级更高),服务端对比If-None-Match和服务端自己的Etag值。

如果一致的话,就返回304告知客户端资源没变,使用缓存吧(2KB)。

如果不一致的话,就返回200告知客户端资源已变,并同时返回新的资源内容和相应的Last-Modified、Expires、Cache-control和Etag(12KB)。

循环以上过程...

注:只有资源本身的内容发生变化了,Etag才会改变。

优点:
解决了http1.0协商缓存--Last-Modified字段时间只能精确到秒的问题和服务器本身资源发生变化才会重新请求的问题。
缺点:
浏览器在缓存有效期内还是无法主动得知服务器资源的变化。

浏览器如何主动得知服务端资源的变化?

上述的缓存过程可谓是完美了,但是还有一个问题,在缓存还没过期的时候服务端的资源发生了更新,此时浏览器还是一直在使用旧的资源(缓存),这怎么办呢?

解决方案就是不让HTML强制缓存。

过程:

HTML请求只使用协商缓存,即每次都要与服务器进行确认。

当HTML中引入的js,css,img等文件有一个文件发生变化时(a.111.js变成了a.222.js),此时的HTML文件内容Etag也会发生变化,会重新请求并返回新的HTML文件。

对于HTML中没有发生变化的文件,在请求时会直接使用本地缓存。

对于HTML中已经发生变化的文件,会重新请求服务端。

这样,我们每次重新请求页面时,就可以感知到每一个文件的变化啦。

Cache-control字段

在讲述上述缓存的过程中,我们只是介绍了Cache-control在响应头中的一种用法,但是Cache-control还可以在请求头中使用,且意义不同。

响应头
  • public: Cache-control:public

放在响应头中表示包含CDN、缓存服务器在内等都可以缓存此次请求的内容。并将该内容返回给其他用户使用。

  • private Cache-control:private

表示该请求内容只能由客户端或者浏览器自己进行缓存,不可以放入公共缓存区。可以有效防止公共缓存存储带有个人信息的响应内容。

  • no-cache Cache-control:no-cache

放在响应头中表示告知客户端或缓存服务器--可以缓存资源,但是不让浏览器或者缓存服务器使用强制缓存,要使用协商缓存向服务器确认资源的新鲜度。

这里有一个问题:max-age=0 与 no-cache是一样的吗?我觉得实现了同样的功能啊,跪求各位大佬独到的见解哈哈

  • no-store

Cache-control:no-store

表示不让浏览器或者缓存服务器对该资源进行缓存。

  • must-revalidate,可以缓存,但是使用之前必须先向源服务器确认。

  • proxy-revalidate,要求缓存服务器针对缓存资源向源服务器进行确认。

  • max-age Cache-control:max-age=30

告知浏览器在30秒之内再次请求时,直接使用缓存即可。

请求头
  • no-cache 该字段是客户端对缓存服务器的指示,不要给我缓存的资源,请把我的请求转发给服务器,确认资源的新鲜度。

  • no-store 暗示缓存服务器不缓存请求(或响应)的任意部分。

  • max-age Cache-control:max-age=30

该字段在请求头中表示,缓存资源的剩余的缓存时间如果不大于30秒的话,就可以把缓存给客户端。

当响应头中有Cache-Control:'no-cache'这个字段时,请求头中会携带相关的Cache-Control字段吗?

  • html文件 会。在学习的过程当中发现,html文件的请求头在页面刷新的时候会携带Cache-Control:max-age=0,这个是浏览器自己的行为,当识别到是html文件时,会自动加一个Cache-Control:max-age=0在请求头中,目的是为了防止取到过期的缓存。

  • 其他文件 不会。其他文件并不会自动携带Cache-Control这个字段。

浏览器缓存举例

研究了一番缓存字段之后,让我们来实践一下吧,由于chrome浏览器会给我们的请求做一些优化(字段以及状态不准确),所以我们直接使用火狐浏览器,直接打开项目的控制台。

在勾选禁用缓存之后,刷新页面,此时的请求都没有使用缓存,全部返回状态200,且后面显示响应内容的大小。

第一次请求:

屏幕快照 2021-06-03 上午11.14.58.png

然后,我们随便打开一个请求,看一下它的请求头和响应头:

屏幕快照 2021-06-03 上午11.18.44.png

响应头可读取信息:

服务器告知浏览器:浏览器将资源缓存在本地,在7776000秒内再次请求请使用强制缓存,超出7776000秒后请使用协商缓存向我确认。

请求头可读取信息:

浏览器不会使用强制缓存或不接受缓存服务器的缓存,它会直接发送请求向浏览器确认资源的新鲜度。

再次请求:

发现此次请求返回304,是协商缓存返回的结果。

屏幕快照 2021-06-03 上午11.26.27.png

请求头中携带了协商缓存的字段,服务器发现与一次请求的响应头中字段一致,返回304。

不同刷新行为所用的缓存

在介绍刷新行为之前,我们先来介绍一下常用的缓存位置,即memory cache、disk cache,分别是内存和磁盘。

memory cache:读取时间快,存储时间短,关闭tab之后就会被释放
disk cache:读取时间慢,存储时间长,在tab关闭之后还可存储,可存储体积比较大的文件

至于,什么资源该存储在哪里,浏览器有它自己的规则,潜在的规则就是哪个利用率低就用哪个。

  • 页面普通刷新行为

没有关闭tab页的基础上进行刷新,可以读取memory cache和disk cache,即可使用强制缓存和协商缓存。

  • 强制刷新

强制刷新表示清除缓存,不使用缓存。

  • 输入URL回车 在已经关闭tab的情况下,memory cache已经消除,如果还有disk cache的话,可以使用,即可使用强制缓存。

浏览器和开发对于缓存都需要做些什么?

前端开发需要做的事情自然是与用户体验有关的,也关于自己维护的js、html和图片文件等。即在用户加载静态文件的时候,能够适当的使用缓存,提高用户体验。在加载其他非静态文件的时候,不需要使用强制缓存。

那浏览器做了些什么?

  • 在识别到某些响应头时,给下一次的请求携带相应的请求头
  • 在用户执行一些刷新行为时,浏览器会自己加上某些请求头,来决定是否要用缓存
  • 不同浏览器对于缓存的实现的方式不同,结果也不一样。

参考文章:

juejin.cn/post/684490… juejin.cn/post/684490…