HTTP缓存

一、 缓存基本原理

定义:首次请求后,浏览器(或代理服务器)会保存一份请求资源的响应副本。当用户再次发起相同请求时,若判断缓存命中,则拦截请求,直接将存储的副本返回给用户,从而避免重新向服务器发起网络请求。

💡缓存的优点

  1. 减少冗余数据传输:节省网络带宽和流量费用。
  2. 减轻服务器负担:大幅降低服务器并发压力,提升网站整体性能。
  3. 提升用户体验:加快客户端网页和资源的加载速度,实现"秒开"。

二、 缓存分类与核心区别

HTTP 缓存主要分为两类:强制缓存协商缓存

维度 强制缓存 (Strong Cache) 协商缓存 (Negotiation Cache)
核心机制 浏览器不向服务器发送请求,直接从本地缓存读取资源。 浏览器向服务器发送请求,询问资源是否更新,由服务器决定是否使用缓存。
状态码表现 200 OK (from memory/disk cache) 命中返回 304 Not Modified,未命中返回 200 OK
适用场景 资源不常变动,或文件名带有 Hash 值的静态资源。 资源频繁变动,且必须保证数据实时性的场景(如 HTML 文件)。

核心痛点引出 :强制缓存直接读取本地数据,如果在此期间服务器上的资源被修改了,用户看到的依然是旧数据。为了解决资源更新 的问题,引入了协商缓存


三、 强制缓存 (Strong Cache)

强制缓存主要依靠响应头(Response Headers)中的参数来控制。

1. Cache-Control (HTTP/1.1,相对时间,优先级高)

当值设置为 max-age=xxx(单位为秒)时,代表在请求正确返回后的 xxx 秒内再次加载资源,会直接命中强制缓存。

常用指令解析:

  • **max-age=xxx**:缓存有效时间,单位为秒。
  • **no-cache**并非不缓存 ,而是不直接使用本地缓存。强制要求使用协商缓存,向服务器确认资源是否更改(若响应中有 ETag,则带上 ETag 去校验)。
  • **no-store**真正的禁止缓存。浏览器不保存任何数据,每次请求都会向服务器下载完整资源。
  • **public**:资源可被所有用户缓存(包括终端用户浏览器和 CDN 等中间代理服务器)。
  • **private**:资源只能被终端用户的浏览器缓存,不允许 CDN 等中间代理服务器缓存(默认值)。
  • **must-revalidate**:缓存必须在使用之前验证其状态,不允许使用过期缓存。

2. Expires (HTTP/1.0,绝对时间,优先级低)

  • 机制 :服务器返回的一个绝对过期时间(如 Wed, 22 Nov 2023 08:41:00 GMT)。
  • 缺陷:受限于本地时间,如果客户端本地时间被修改,会导致缓存判断失效。
  • 优先级 :当 Cache-ControlExpires 同时存在时,**Cache-Control** 优先级高于 **Expires**

3. 强制缓存的读取位置

在 Chrome Network 面板中,强缓存命中会显示 200,但来源分为两种:

  • **from memory cache**:从内存中读取。速度极快,但关闭浏览器标签页后缓存即失效。
  • **from disk cache**:从硬盘中读取。速度稍慢,但持久化存储,关闭浏览器后依然存在。

四、 协商缓存 (Negotiation Cache)

当强制缓存失效(或未设置强制缓存、设置了 no-cache)时,浏览器会携带缓存标识向服务器发起请求,由服务器决定是否返回 304。

1. 基于最后修改时间:Last-Modified / If-Modified-Since

  • 首次请求 :服务器响应头带上 Last-Modified,记录资源在服务器上的最后修改时间 (精度到)。

  • 再次请求 :浏览器请求头带上 If-Modified-Since,值为之前缓存的 Last-Modified

  • 服务器校验 :对比资源的最新修改时间与 If-Modified-Since

    • 相同 :资源未修改,返回 **304**,浏览器读取本地缓存。
    • 不同 :资源已修改,返回 **200** 及新资源。

2. 基于文件内容 Hash:ETag / If-None-Match

  • 首次请求 :服务器响应头带上 ETag,值为资源文件的唯一标识/Hash值(生成规则由服务器决定)。

  • 再次请求 :浏览器请求头带上 If-None-Match,值为之前缓存的 ETag

  • 服务器校验 :对比当前资源的 ETag 与 If-None-Match

    • 相同 :命中协商缓存,返回 **304**
    • 不同 :未命中,返回 **200** 及新资源。

3. 🆚 ETag 与 Last-Modified 深度对比

对比维度 Last-Modified ETag
精度 秒级。如果文件在 1 秒内被修改多次,无法识别。 内容级。只要文件内容改变,Hash 值必变,精度极高。
性能 。只需读取文件的最后修改时间,开销极小。 。需要计算文件内容的 Hash 值,会加大服务器 CPU 开销。
优先级 较低。 较高 。服务器校验时优先以 ETag 为准
适用场景 文件较大且修改频率不高的普通资源。 文件较小、修改频繁,或需要极高精度校验的资源。

💡 毫秒级修改问题 :如果一个资源在 1 秒内被修改了多次,Last-Modified 无法感知变化,必须配合 ETag 使用才能确保拿到最新数据。


ETag/If-None-Match 的出现主要解决了 Last-Modified/If-Modified-Since 所解决不了的问题:

  • 如果文件的修改频率在秒级以下,Last-Modified/If-Modified-Since 会错误地返回 304
  • 如果文件被修改了,但是内容没有任何变化的时候,Last-Modified/If-Modified-Since 会错误地返回 200,上面的例子就说明了这个问题

五、 缓存策略与最佳实践 (重点补充)

在实际的前端工程化开发中,通常会组合使用强制缓存和协商缓存,以达到性能与数据一致性的最佳平衡:

1. HTML 文件:使用【协商缓存】

  • 策略 :设置 Cache-Control: no-cache
  • 原因:HTML 是页面的入口,如果 HTML 被强缓存,即使 JS/CSS 更新了,用户也无法加载到新的 HTML,导致页面无法更新。使用协商缓存可以保证每次都能拿到最新的 HTML 结构。

2. 静态资源 (JS/CSS/图片/字体):使用【长期强制缓存】

  • 策略 :设置 Cache-Control: max-age=31536000(一年),并在打包时给文件名加上 ContentHash (如 app.a1b2c3d4.js)。
  • 原因:只要文件内容不变,文件名就不变,直接命中强缓存,实现"秒开";一旦文件内容修改,Hash 值改变,文件名改变,浏览器会认为是新资源从而重新请求,完美避开缓存不更新的问题。

3. 频繁变动的 API 数据:【不缓存】或【短期缓存】

  • 策略 :设置 Cache-Control: no-store 或极短的 max-age
  • 原因:保证用户每次都能看到最新的业务数据。

六、 补充注意事项 (避坑指南)

  1. 请求方法限制 :浏览器只对 **GET** 请求进行缓存POSTPUTDELETE 等请求默认不会被缓存。
  2. URL 参数影响 :缓存是基于完整 URL 的。如果 URL 带了不同的查询参数(如 ?v=1?v=2),浏览器会认为是两个不同的资源,从而导致缓存失效
  3. 手动清除缓存 :在代码中无法直接清除用户的浏览器缓存,只能通过更改文件名(加 Hash)更改 URL 参数(加时间戳/版本号) 来"绕过"旧缓存,这被称为"缓存破除(Cache Busting)"。
  4. HTTPS 与缓存:HTTPS 下的缓存机制与 HTTP 基本一致,但某些中间代理(如共享代理)出于安全考虑,可能不会缓存 HTTPS 响应,此时主要依赖浏览器本地缓存。

七、常见面试题解析

1. 强缓存和协商缓存有什么区别?

  • 核心区别:是否发送 HTTP 请求到服务器
  • 强缓存 :不发请求,直接用本地缓存,状态码 200 (from cache)
  • 协商缓存:发请求验证,服务器返回 304 则用缓存,返回 200 则更新缓存

2. no-cacheno-store 有什么区别?

  • **no-cache**:不是不缓存,而是每次使用前必须去服务器验证有效性(协商缓存)
  • **no-store**:完全禁止缓存,每次请求都从服务器获取完整资源,且响应内容也不被缓存

3. F5 刷新和 Ctrl+F5 强制刷新有什么不同?

  • F5 刷新 :浏览器会跳过强缓存阶段,但会带上协商缓存头(If-Modified-Since/If-None-Match)去服务器验证资源有效性
  • Ctrl+F5 强制刷新 :彻底绕过所有缓存,请求头带Cache-Control: no-cachePragma: no-cache,强制从服务器获取最新资源
相关推荐
JAVA社区1 小时前
Java进阶全套教程(四)—— SpringMVC框架详解
java·开发语言·spring·面试·职场和发展
Gh0st_Lx1 小时前
【9】面试官:讲一下MySQL 和 Redis 的缓存一致性问题
redis·mysql·缓存
绝知此事2 小时前
Netty实战:从零构建高性能TCP通信服务(含心跳检测)
java·网络·spring boot·网络协议·tcp/ip
发现一只大呆瓜2 小时前
超全 Vite 性能优化指南:网络、资源、预渲染三维落地方案
前端·面试·vite
Raink老师3 小时前
【AI面试临阵磨枪-58】AI 生成内容合规、版权、审核机制设计
人工智能·面试·职场和发展
身如柳絮随风扬3 小时前
Redis 主从复制与哨兵机制详解:从原理到高可用实战
数据库·redis·缓存
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题 第71题】【Mysql篇】第1题:索引是什么?
java·开发语言·b树·mysql·面试
发现一只大呆瓜6 小时前
Vite 兼容降级全解:语法降级、Polyfill 原理与 legacy 插件底层机制
前端·面试·vite
AI人工智能+电脑小能手7 小时前
【大白话说Java面试题 第70题】【JVM篇】第30题:垃圾回收器是怎样寻找 GC Roots 的?
java·开发语言·jvm·面试