前端http缓存详解

http缓存

HTTP 缓存会存储与请求关联的响应,并将存储的响应复用于后续请求。

缓存的原理是在首次请求后保存一份请求资源的响应副本,当用户再次发起相同请求时,判断缓存是否命中,如果命中则将前面的响应副本返回给用户。

使用缓存可以减少HTTP请求的数量,减少HTTP响应的大小,使web页面加载得更快。

如上图,在Chrome的开发者工具中,Network的size列可以看到部分数据来自memory cache和disk cache

  • memory cache:即内存缓存,存储在浏览器内存中,在访问页面后,再次刷新页面,可以发现很多数据来自Memory Cache(即内存缓存)

    • 优点:获取速度快;优先级高,当资源加载时,浏览器率先查找内存(memory cache)是否存在,存在则直接加载,不存在则找磁盘(disk cache)是否存在,存在则获取,不存在就进行网络请求。
    • 缺点:生命周期短,当网页的tab页面被关闭后(刷新不会),内存就会释放;且计算机内存的大小有限
  • disk cache:即磁盘缓存,存储在计算机硬盘中

    • 优点:存储容量大;生命周期长,只要不删除就一直存在
    • 缺点:读取速度相对memory cache较慢

http缓存分为两种缓存,分别是强制缓存协商缓存

强制缓存

强制缓存,也称强缓存。

强制缓存的过程是:客户端请求数据时判断有没有缓存数据存在,存在的话再判断缓存数据有没有过期,如果没有过期则直接使用缓存数据,无需与服务端进行通信。

如何判断缓存数据有没有过期:通过在服务端响应头中设置相关的缓存策略信息(具体为标明失效规则的字段),可以控制缓存数据的过期时间。

标明失效规则的字段有如下:

Expires

Web服务器使用Expires头来告诉Web客户端它可以使用某个资源的缓存数据(副本)直到指定的时间为止

例如上图表示该资源在2024年3月7日 18点38分56秒之后缓存失效,在没超过这个时间的时候,缓存都是命中(即有效)的。如果页面的一个图片返回了这个响应头部,浏览器会缓存该图片数据,在后续的页面浏览中使用缓存的图片数据,直到超过Expires指定的时间。

Expires是HTTP1.0的产物,因为该字段使用的是特定的时间(绝对时间),该时间是服务端的时间,浏览器会将其与客户端的时间进行比对,这就可能导致客户端的时间和服务端的时间不一致,因为用户可以随意修改客户端的时间,这就会导致缓存命中的误差。

所以HTTP1.1引进了Cache-Control头来克服Expires头的限制。

Cache-Control

Cache-Control的常用属性有

  • max-age

  • private/public

  • no-cache

  • no-store

  • s-maxage

一个一个详细说明

max-age

Cache-Control使用max-age可以指定缓存的生命周期(即被缓存多久)。它以秒为单位定义来一个更新窗。

如上图,表示该资源从第一次请求返回时开始,往后2592000秒(720小时)内如果再次请求该资源则从缓存中读取。由于Cache-Control的max-age明确指出来相对于请求时间所经过的秒数,时间同步问题就被避免了。

而且Cache-Control是HTTP1.1的产物,其优先级比Expires高。如果两者同时出现,HTTP规范规定Cache-Control的max-age指令将重写Expires头。

private/public

arduino 复制代码
Cache-Control: public

public表示该响应可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存

arduino 复制代码
Cache-Control: private

private:私有缓存,表示该响应只能被发送请求的客户端缓存,而代理服务器等等其他不能缓存

private/public 未设置时,默认为private

no-cache和no-store

yaml 复制代码
Cache-Control: no-cache

no-cache 不是禁止一切缓存的意思,而是强制客户端向服务端发送请求判断资源是否变更,变更则返回变更后的数据,未变更则返回304。即no-cache是禁止没有重新验证的情况下使用缓存,可以理解为不使用强缓存,使用协商缓存

yaml 复制代码
Cache-Control: no-store

no-store 表示该响应禁用缓存,不将响应存储在任何缓存中。

s-maxage

s-maxage:"s"表示共享,该字段和max-age类似,用于设置缓存时长,优先级高于max-age或Expires,但该字段仅在代理服务器(如CDN等)生效,仅当设置了public才有效。

协商缓存

协商缓存(也被称为对比缓存)

强缓存的优先级比协商缓存高。当强制缓存没有命中(例如max-age过期),或者Cache-Control设置了no-cache(上文说到的),则开始进行协商缓存。

使用协商缓存时,客户端会携带缓存数据标识会向服务端发送一个请求,服务端识别对应的数据是否失效,如果未失效则返回304状态码,通知客户端比对成功,可以使用缓存数据。如果失效则返回最新数据,状态码为200。

有个问题是反正都要发生请求,为什么还要使用缓存?

因为如果判断结果缓存有效,则可以使用缓存,减少响应数据的大小,加快响应速度。如下图

第一次获取:

第二次获取:

那么协商缓存过程中,服务端是怎么判断缓存数据是否有效的呢,主要通过以下几个字段

Last-Modified和If-Modified-Since

客户端首次请求一个资源时,服务端返回的header中如果有加上Last-Modified,其表示该资源的最后修改时间,如下图,该资源的最后修改时间为2024年2月24日 12点29分00秒

那么当客户端后续再次请求该资源时,会在发送的请求头中携带If-Modified-Since,其值和之前返回的Last-Modified一致。如下图

这样服务端就会收到If-Modified-Since值,再根据资源最新的最后修改时间进行判断缓存是否失效。如果缓存命中(缓存有效),则返回空响应体, 状态码为304,也不会返回Last-Modified字段。如果缓存失效,则返回最新数据,并返回Last-Modified字段,状态码为200。

Last-Modified和If-Modified-Since是HTTP1.0的产物,其实其判断规则是有一定缺陷的

一,Last-Modified和If-Modified-Since的检查时间最小单位是秒,如果资源在一秒以下的时间完成了更新,则资源最后修改时间(Last-Modified)不变,但实际资源已经变了,这时客户端的缓存是失效的,然而这种情况服务端依然会判断缓存命中。

二,如果资源进行了编辑操作,但是实际资源的内容没有修改,这时最后修改时间(Last-Modified)变了,这种情况服务端判断缓存失效,重新返回了新数据。而实际上是不需要的,因为文件内容没变。

所以对于以上的问题,HTTP1.1用了Etag和If-None-Match作为Last-Modified和If-Modified-Since的补充

Etag和If-None-Match

Etag是由服务端生成的用于标识资源的唯一标识符。

浏览器首次请求一个资源时,服务端根据资源内容生成一段hash字符串,加在返回的header中的Etag字段上,如下图

那么当客户端后续再次请求该资源时,会在发送的请求头中携带If-None-Match,其值和之前返回的Etag一致。如下图

这样服务端就会收到If-None-Match值,并再次读取资源内容的Etag值进行匹配。如果缓存命中(缓存有效),则返回空响应体, 状态码为304。如果匹配失败,则缓存失效,返回最新数据,包括新的Etag字段,状态码为200。

Etag和If-None-Match的校验优先级比Last-Modified和If-Modified-Since高。

但是由于Etag的生成需要服务端的计算开销,资源越大越多,开销就越重,会影响服务端的性能。所以Etag和If-None-Match并不是用来取代Last-Modified和If-Modified-Since的,只是用来作为其的补充方案,具体用哪一种需要根据具体的业务场景决定。

浏览器用户行为与缓存

地址栏回车、页面链接跳转、页面前进/后退等等这些用户行为都是按照正常的缓存检查流程进行

而当用户点击刷新按钮时,浏览器会对本地的缓存文件过期,但是If-Modified-Since和If-None-Match还是会带上,意味着还是可能进行协商缓存

当用户进行强制刷新的操作时,浏览器不仅会对本地的缓存文件过期,而且也不会带上If-Modified-Since和If-None-Match,意味着相当于全部重新第一次请求。

相关推荐
轻口味3 小时前
Vue.js `v-memo` 性能优化技巧
前端·vue.js·性能优化
prince_zxill3 小时前
Array.prototype 方法在复杂数据处理中的应用
前端·javascript·原型模式
GISer_Jing3 小时前
React基础知识回顾详解
前端·react.js·前端框架
阿正的梦工坊3 小时前
深入解析 Chrome 浏览器的多进程架构:标签页是进程还是线程?(中英双语)
linux·服务器·前端·chrome·架构·unix
无限大.3 小时前
前端知识速记--CSS篇:display
前端·css
小松聊PHP进阶4 小时前
万字总结PHP与JavaScript、PHP与PHP 实现开箱即用的AES、RSA和较为安全的自定义加解密算法
前端·后端·php
滚雪球~4 小时前
el-button 中icon在文字前和在文字后的写法
前端
半世轮回半世寻5 小时前
Nuxt后端接口实战:从0到1连接MongoDB数据库
前端
小乌龟快跑5 小时前
React 设计实现一个支持动态插槽的Layout组件
前端·面试
打野赵怀真5 小时前
行内元素和块级元素有什么区别,如何相互转换?
前端·javascript