我们在做web开发时,有一项需要特别关注的点,就是性能优化。
而前端性能优化中最核心的就是 web 缓存和 http 协议标头。
性能优化直接影响了用户的体验,如果一个网页加载速度非常缓慢,那么会让用户感到沮丧,甚至可能会让用户离开网站。
为了提高用户访问网站的响应速度,我们通常使用缓存来进行优化。
缓存通常可以使用在服务器端,比如 redis 或者 memcached。
也可以用在浏览器端,存储在用户电脑的内存或磁盘上。
当网页需要资源时,首先访问 web 缓存,如果缓存中没有,那么就会请求服务器端,服务器端进行响应,浏览器端同时会把资源缓存起来。
当网页再次需要这个资源时,同样会访问 web 缓存,这时缓存中已经存在这个资源,就会将这个资源直接返回。
这样有几个好处。
首先请求不会再次请求服务器,响应速度大大提升。
其次,服务器的带宽占用也会大大降低。
第三,服务器的负载占用也会大大降低。负载主要指的是CPU、内存等硬件资源。
现在我们知道了缓存是什么以及缓存的好处。
接下来我们再来看看缓存的位置。
缓存的位置主要有三个地方。
首先是浏览器缓存。
其次是代理服务器,它是代理级别的缓存。
最后是反向代理服务器,它是距离我们源服务器最近的代理,内容也可能缓存在这里。
关于代理服务器和反向代理服务器的介绍就不在这里详细展开了,因为它们的内容过于庞大,并且和我们这期视频的内容关系不大。
现在我们知道了什么是缓存,以及缓存存放的位置。接下来我们开始正式学习缓存。
在学习缓存之前,我们要了解几个概念。
第一个是浏览器,它是资源的请求方,当然也可以是其他类似的应用程序。
第二个是源服务器,也就是内容真正来源的服务器。我们要在这台服务器上产生内容,然后缓存到任何位置。
第三个是新鲜度,缓存通常是有时长限制的,比如 30 秒内都有效。这个有效时间就是新鲜度。
第四个是校验值,当缓存过期之后,会继续请求源服务器上的内容,然后拿缓存内容和最新的内容进行比较。如果没有什么变化,那么就更新缓存的过期时间。
第五个是缓存失效,如果缓存过期,并且有了新的内容,那么就会删除原有的缓存,并将最新的内容存储到缓存中。
了解了上面的五个概念之后,我们再了解一个极其关键的概念,http 标头。
因为浏览器和服务器之间通信的主要协议就是 http,所以内容的缓存策略都在 http 标头中。
换句话说,你了解了 http,就明白我们应该如何在浏览器端进行缓存内容。
每当接收到浏览器发送的请求时,服务器都会在响应报文中设置一段标头,报文中会包含很多标头。
其中有几个是新鲜度相关的标头。
最常见的是:expires、pragma、cache-control。
其中 expires 和 pragma 是在 http1.1 之前就存在的,通常用于浏览器的向后兼容。
cache-control 是 http1.1 引入的标头,它同样可以做 expires 和 pragma 同样的事情。
还有几个是校验值相关的标头。etag、if-none-match、last-modified、if-modified-since。
首先来看新鲜度相关的三个标头标头。
expires 的值是一个时间,但不可以超过一年。如果你设置的不是一个时间,或者时间格式错误,那么就不会起到任何作用。
pragma 的值只能是 no-cache,它用来防止缓存。只要设置了这个标头,这个请求就不会被缓存。
cache-control 是目前使用缓存的首选项。和 expires、pragma 不同的是,它是一个多值标头。
cache-control 支持的值有 private、public、no-store、no-cache、max-age、s-max-age。
private 表示内容是对用户私有的,只会在浏览器缓存,而不会在代理服务器中缓存。
public 表示内容可以在任何位置缓存,比如代理服务器。
no-store 表示内容不可以被任何人缓存。
no-cache 表示内容可以被缓存,但是客户端每次都需要请求服务器进行内容校验。
max-age 表示缓存内容的过期时间。它的单位是秒。使用秒作为过期时间会比 expires 的时间字符串更加友好。
s-max-age 表示共享缓存的过期时间,代理服务器会使用这个时间。如果 max-age 和 s-max-age 同时存在,浏览器会使用 max-age,而代理服务器则会使用 s-max-age。
must-revalidate,在某些情况下,服务器无法访问,浏览器会使用缓存的内容,哪怕这个内容的有效期已经超过了 max-age 的时间。设置了 must-revalidate 后,超过 max-age 的缓存必须请求服务器。
proxy-revalidate 和 must-revalidate 的作用基本相同,不过它是应用于代理服务器的。
上面就是关于新鲜度相关标头的介绍。
接下来我们再来看校验值相关标头。
首先是 ETag。
ETag 是 entity tag 的缩写,表示实体标签。这个标头的内容是资源的唯一标识符。
当一个资源的新鲜度过期后,再次请求服务器会附带 if-none-match,值为上一次返回标头的 ETag。
服务器收到请求后,会使用最新的内容和 if-none-match 进行对比,如果不相同,返回新的 ETag 和新资源。如果相同,返回 304 状态码,表示没有发生任何变化。
ETag 有两种类型,强和弱。强 ETag 表示两次内容完全相同,弱会有一个 W/ 前缀,表示两次内容虽然不严格相同,但可以认为是相同的。
last-modified 表示最后一次修改内容的日期和时间。当内容陈旧时,浏览器将在请求中使用最后一次的 last-modified 作为 if-modified-since 的值。服务器会根据内容最后的修改时间和这个时间做对比,如果不一致,会返回新的内容和新的 last-modified。如果一致,返回 304。
如果返回标头中同时存在 ETag 和 last-modified。那么请求标头也会同时附带 if-none-match 和 if-modified-since,服务器会同时校验这两个标头。
接下来我再解释一下什么是强缓存和协商缓存。
强缓存其实就是指的新鲜度,也就是 expires、pragma 和 cache-control。
协商缓存其实指的是校验值,也就是 ETag 和 last-modify。
当我们使用 f5 对页面进行刷新时,会禁止强缓存,但协商缓存正常生效。
当我们使用 ctrl+f5 强制刷新时,会暂时禁止强缓存和协商缓存,请求一定会到达服务器。
以上就是缓存相关的所有 http 标头了。
如果你要问我的网站该设置哪个缓存标头呢?没有标准答案,要根据不同类型的站点来做不同的缓存策略。
一种比较通用的做法是,html 文件使用协商缓存。
html 中引用的 js、css 和其他资源,使用强缓存,可以设置一年的有效期。但是每次对代码修改后进行编译时,使用文件指纹,动态的生成这些文件名。这样就可以做到每次修改都可以获取到最新的资源引用。
最后是接口,每个行业对接口数据的及时性都有着不同的需求。比如购物类的商品接口数据,我们通常不允许使用缓存。比如咨询类的接口数据,可以使用强缓存或者协商缓存。具体采用何种方案,要符合场景、因地制宜。
如果只是单纯在概念上学习缓存知识,难免有些过于抽象。
学代码,还是要真正落地到代码上面。
我使用 express 实现了一些常见的场景。源码我放在文稿中了:github.com/luzhenqian/…,感兴趣的同学可以去看一下。
以上就是关于 web 缓存和 http 标头所有内容。希望看完这个视频,你能有所收获。