🐶深入HTTP缓存机制及不同场景下的效果表现

900 阅读7分钟

前言:

这里讨论的缓存指是浏览器将用户请求过的静态资源(html、css、js、images),存储到浏览器内存或者电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载了,不需要再去服务端请求了。

1 启用缓存的优缺点

1.1 优点:

减少了不必要的数据传输,节省带宽

减少服务器的负担,提升网站性能

加快了客户端加载网页的速度

用户体验友好

1.2 缺点:

资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕。

2 缓存原理(强制缓存和协商缓存)

浏览器是如何判断 哪些资源 需要缓存哪些不需要缓存?又是如何判断 资源是从缓存取还是从服务器更新呢?为此我们首先需要理解以下两个概念:

2.1 强制缓存(cache-control)

强制缓存;当浏览器去请求某个文件的时候,服务端就在respone header里面对该文件做了缓存配置。缓存的时间、缓存类型都由服务端控制,具体表现为:

respone header 的cache-control,常见的设置是max-age public private no-cache no-store等

  • max-age 表示缓存的时间是31536000秒(1年);
  • public 表示可以被浏览器(客户端)和代理服务器缓存,代理服务器一般可用nginx来做;
  • immutable 表示该资源永远不变;

immutable 配置实际上该资源并不是永远不变,它这么设置的意思是为了让用户在刷新页面的时候不要去请求服务器。

如果你只设置了cahe-control:max-age=31536000,public 这属于强缓存,每次用户正常打开这个页面,浏览器会判断缓存是否过期,没有过期就从缓存中读取数据;

如果cahe-control:max-age=315360000,public再加个immutable的话,就算用户刷新页面,浏览器也不会发起请求去服务,浏览器会直接从本地磁盘或者内存中读取缓存并返回200状态,看上图的红色框(from memory cache)。

image.png

这是2015年facebook团队向制定 HTTP 标准的 IETF 工作组提到的建议:他们希望 HTTP 协议能给 Cache-Control 响应头增加一个属性字段表明该资源永不过期,浏览器就没必要再为这些资源发送条件请求了。

cache-control: max-age=xxxx,public

客户端和代理服务器都可以缓存该资源;

客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,如果用户做了刷新操作,就向服务器发起http请求

cache-control: max-age=xxxx,private

只让客户端可以缓存该资源;代理服务器不缓存

客户端在xxx秒内直接读取缓存,statu code:200

cache-control: max-age=xxxx,immutable

客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,即使用户做了刷新操作,也不向服务器发起http请求

cache-control: no-cache

跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存的,设置了no-cache就不会走强缓存了,每次请求都回询问服务端。

cache-control: no-store

不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存、协商缓存了。

2.2 协商缓存

强缓存就是给资源设置个过期时间,客户端每次请求资源时都会看是否过期;只有在过期才会去询问服务器。所以,强缓存就是为了给客户端自给自足用的。而当某天,客户端请求该资源时发现其过期了,这是就会去请求服务器了,而这时候去请求服务器的这过程就可以设置协商缓存。这时候,协商缓存就是需要客户端和服务器两端进行交互的。

 

协商缓存设置方式:在response header里面的设置

// 类似于文件hash(是服务器给资源添加的唯一标识)
etag: '5c20abbd-e2e8'
// 文件的修改时间
last-modified: Mon, 24 Dec 2018 09:49:49 GMT 

每次请求返回来 response header 中的 etag和 last-modified,在下次请求时在 request header 就把这两个带上,服务端把你带过来的标识进行对比(etag 优先级更高,其次是 last-modified,也就是先比对 etag,如果 etag 变了,就再去比对 last-modified),然后判断资源是否更改了,如果更改了(etag和 last-modified都变了)就直接返回新的资源,状态200,和更新对应的response header的标识etag、last-modified;如果资源没有变,那就不变etag、last-modified,这时候对客户端来说,每次请求都是要进行协商缓存,从本地缓存中取资源,状态码 304。

2.2.1 etag 和 last-modified 作用

原因一: 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;

原因二: 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是秒级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);

某些服务器不能精确的得到文件的最后修改时间。

这时,利用Etag能够更加准确的控制缓存,因为Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符。

Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

2.3 区别

强缓存与协商缓存的区别:

缓存控制标识获取资源形式状态码发送请求到服务器
强制缓存Expires / Cache-Control从缓存取200(from cache)否,直接从缓存取
协商缓存Etag / Last-modified从缓存取304(not modified)是,通过服务器告知浏览器缓存是否可用

3 延伸知识:

理解缓存的 from memory cache 和 from disk cache

状态类型说明
200form memory cache不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中;
200from disk cache不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等;
200资源大小数值从服务器下载最新资源;
304报文大小请求服务端发现资源没更新(not modified),使用本地缓存;

3.1 三级缓存原理:

  1. 先去内存(form memory cache)看,如果有,直接加载;

  2. 如果内存没有,则从硬盘(from disk cache)获取,如果有直接加载;

  3. 如果硬盘也没有,那么就进行网络请求;

  4. 加载到的资源缓存到硬盘和内存。

所以我们可以来解释这个现象,以图片📷为例(结合下面 流程图1 理解):

场景一:

访问-> 200 -> 退出浏览器;


再进来-> 200(from disk cache) -> F5 刷新 -> 200(from memory cache) -> Ctrl + F5 刷新 -> 200

3.2 一些结论(谷歌浏览器为例):

1.0 只要图片是base64 都是from memroy cache;

2.0 隐私模式下,几乎都是 from memroy cache;

4 用户操作行为对缓存的影响

用户操作(强制缓存) Expires/Cache-Control(协商缓存) Last-Modified/Etag
地址栏回车有效有效
页面链接跳转有效有效
新开窗口有效有效
前进后退有效有效
F5刷新无效有效
Ctrl+F5强制刷新无效无效

image.png 流程图1

5 项目中应用

5.1 后端服务器如 nodejs:

设置 协商缓存:

res.setHeader('max-age': '3600 public')
res.setHeader(etag: '5c20abbd-e2e8')
res.setHeader('last-modified': Mon, 24 Dec 2018 09:49:49 GMT)

5.2 nginx配置:

image.png

5.3 策略

index.html文件采用协商缓存;其他 js,css,image 静态资源采用 强缓存+协商缓存;