当前位置: 首页
AI教程
HTTP缓存机制详解强制缓存与协商缓存工作原理

HTTP缓存机制详解强制缓存与协商缓存工作原理

热心网友 时间:2026-05-28
转载

HTTP协强制缓存与协商缓存详解

聊到Web性能优化,缓存是绕不开的核心话题。但很多开发者一听到“强制缓存”、“协商缓存”这些术语就头疼,觉得是面试八股文,离实际开发很远。今天,我们就来彻底拆解一下HTTP缓存的两层架构,看看它们到底是怎么工作的,以及在实际项目中,尤其是Spring Boot生态下,我们到底该怎么用。

一、先搞懂:HTTP 缓存的两层架构

HTTP缓存机制其实是一个清晰的递进流程,分为强制缓存和协商缓存两层。简单来说,浏览器发起请求时,会先问自己:“本地缓存还能用吗?” 如果能用(强制缓存命中),就直接从本地读取,连请求都不发。如果不能用(强制缓存未命中),它才会去问服务器:“我手上的这个版本,你还认吗?” 这就是协商缓存。

1.1 强制缓存:浏览器自己说了算

强制缓存的核心逻辑非常霸道:只要浏览器判断缓存没过期,就直接使用本地资源,完全不会向服务器发送任何请求。这就像是给浏览器发了一张“通行证”,在有效期内,它自己说了算。

这张“通行证”主要通过两个响应头来签发:

  • Cache-Control: max-age=86400:这是相对时间,从服务器响应返回的那一刻开始计时,86400秒(也就是1天)内有效。
  • Expires: Wed, 28 May 2026 10:00:00 GMT:这是绝对时间,告诉浏览器在这个时间点之前,缓存都是有效的。

这里有个关键区别:Cache-Controlmax-age不依赖客户端的本地时间,它是通过计算“服务器响应时间 + max-age”来得到过期时间的,因此稳定性远高于依赖客户端时钟的Expires。当两者同时存在时,Cache-Control的优先级更高。

当强制缓存命中时,你在浏览器开发者工具的网络面板里,会看到状态码显示为200 OK (from disk cache)200 OK (from memory cache),这表示资源根本没经过网络。

1.2 协商缓存:服务器最终说了算

强制缓存虽好,但有个致命问题:一旦“通行证”过期,不管服务器上的资源有没有真正更新,浏览器都得重新下载完整的资源。这显然不够聪明。

协商缓存就是为了解决这个问题而生的。它的核心是“协商”:当强制缓存失效后,浏览器会带着一些“凭证”去问服务器,服务器根据这些凭证判断资源是否变化,再决定是让浏览器继续用旧缓存,还是返回新资源。

二、协商缓存核心原理详解

协商缓存有两种独立的实现机制,它们可以单独使用,也可以协同工作。

2.1 基于时间的实现:Last-Modified / If-Modified-Since

这是最古老也最简单的一种方式,基于资源的最后修改时间。

流程是这样的:

  1. 首次请求:服务器在返回资源时,会在响应头里加上Last-Modified: Wed, 27 May 2026 10:00:00 GMT,告诉浏览器这个文件最后是什么时候被改动的。
  2. 再次请求:当强制缓存过期后,浏览器再次请求这个资源时,会在请求头里带上If-Modified-Since: Wed, 27 May 2026 10:00:00 GMT,把上次拿到的时间戳还给服务器。
  3. 服务器判断:服务器对比当前资源的最后修改时间和请求头里的时间戳。
    • 如果两者相等,说明资源没变,服务器就返回一个轻量的304 Not Modified响应,告诉浏览器:“接着用你缓存里的吧。”
    • 如果不相等,说明资源更新了,服务器就返回200 OK和全新的资源内容,同时更新Last-Modified头。

但这个机制有几个明显的缺点:精度只有秒级,一秒内的多次修改无法识别;有时候文件内容没变,只是被重新保存了一下,修改时间变了,会导致不必要的重新下载;另外,有些服务器可能无法精确获取文件的修改时间。

2.2 基于唯一标识的实现:ETag / If-None-Match

为了解决Last-Modified的不足,HTTP/1.1引入了ETag(实体标签)机制。它不关心时间,只关心内容。

它的工作流程和基于时间的类似,但凭证变了:

  1. 首次请求:服务器根据资源内容(比如计算一个MD5哈希值)生成一个唯一标识,通过响应头ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"发给浏览器。
  2. 再次请求:浏览器在请求头里带上If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
  3. 服务器判断:服务器重新计算当前资源的ETag,和请求头里的值对比。
    • 一致则返回304
    • 不一致则返回200和新资源。

ETag还分两种类型,适用于不同场景:

类型 格式 对比精度 适用场景
强 ETag ETag: "abc123" 字节级精确对比 绝大多数场景
弱 ETag ETag: W/"abc123" 语义级对比(允许注释、空格等微小差异) 对性能要求极高的场景

2.3 优先级与对比

如果服务器同时提供了ETagLast-Modified,那么ETag拥有绝对的优先权。服务器会先检查If-None-Match(ETag),只有在ETag验证通过或者不存在时,才会去检查If-Modified-Since(Last-Modified)。

2.4 你不知道的 304 响应细节

很多人以为304就是个状态码,其实它背后有几个非常重要的特性,正是这些特性让它能节省带宽:

  • 只返头,不返体:304响应只包含响应头,没有响应体。这就是为什么一个304响应可能只有几百字节,却能省下几十KB的资源下载。
  • 会更新缓存过期时间:如果304的响应头里包含了新的Cache-ControlExpires,浏览器会用这些新值来更新本地缓存的过期时间。
  • 不更新验证标识:304响应不会更新资源的ETagLast-Modified值,这些标识只有在返回200 OK的新响应时才会被更新。

2.5 不同刷新行为对缓存的影响(面试必问)

这是最容易被忽略但极其实用的知识点,浏览器的不同操作会触发完全不同的缓存策略:

操作 强制缓存 协商缓存 行为说明
地址栏回车 / 链接跳转 ✅ 生效 ❌ 不触发 优先使用强制缓存,过期才走协商
F5 刷新 / 点击刷新按钮 ❌ 失效 ✅ 生效 跳过强制缓存,直接发起协商请求
Ctrl + F5 强制刷新 ❌ 失效 ❌ 失效 完全跳过所有缓存,请求头不带任何缓存标识,强制服务器返回完整资源

三、一张图看懂完整 HTTP 缓存流程

理论说了这么多,不如一张图来得直观。下面这张流程图清晰地展示了从浏览器发起请求到最终渲染的完整缓存决策过程,涵盖了所有可能的分支情况:

exported_image.png

四、Spring Boot 缓存实战:零代码到自定义

很多人觉得协商缓存实现起来很复杂,需要写一堆判断逻辑。其实在Spring Boot里,框架已经为我们做了绝大部分工作,很多时候你甚至感知不到它的存在。

4.1 Spring Boot 默认缓存机制

Spring Boot默认就为所有静态资源(JS、CSS、图片、字体等)开启了协商缓存支持:

  • 自动生成Last-Modified:基于文件的最后修改时间。
  • 自动生成ETag:基于文件内容的哈希值(通常是MD5)。
  • 默认没有开启强制缓存:这意味着每次请求都会走一遍协商缓存的流程,检查资源是否变化。

换句话说,只要你用Spring Boot,你的静态资源就已经在享受协商缓存带来的好处了,而你一行代码都没写。

4.2 基础配置:application.yml

想要更进一步,开启强制缓存来获得极致性能?只需要在application.yml里加几行配置:

spring:
  resources:
    cache:
      # 开启缓存
      cachecontrol:
        # 静态资源强制缓存1年(31536000秒)
        max-age: 31536000
        # 允许CDN和浏览器缓存
        cache-public: true
        # 资源过期后必须和服务器协商
        must-revalidate: true
      # 开启ETag生成(默认开启)
      use-etag: true
      # 开启Last-Modified生成(默认开启)
      use-last-modified: true

配置说明:

  • max-age: 31536000:为静态资源设置长达1年的强制缓存,这是行业内的常见做法。
  • cache-public: true:允许中间袋里(如CDN、网关)缓存这些资源。
  • must-revalidate: true:这是一个安全网,确保强制缓存过期后,浏览器必须与服务器协商,而不能使用过期的缓存。

4.3 进阶:自定义静态资源缓存规则

一刀切的缓存策略并不合理。HTML文件、带哈希的JS文件、接口文档,它们的更新频率天差地别。我们可以通过实现WebMvcConfigurer接口来为不同资源定制策略:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 1. 打包后的前端资源(带哈希值):强制缓存1年
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic().mustRevalidate());

        // 2. HTML文件:不使用强制缓存,每次都走协商缓存
        registry.addResourceHandler("/*.html")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.noCache());

        // 3. 接口文档:缓存1小时
        registry.addResourceHandler("/doc.html")
                .addResourceLocations("classpath:/META-INF/resources/")
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
    }
}

这就是最佳实践:

  • 带哈希的静态资源(如app.abc123.js):设置超长强制缓存(如1年)。因为文件名哈希变了就是新文件,旧缓存自然失效。
  • HTML入口文件:设置no-cache,每次都走协商缓存。确保用户总能拿到最新的页面骨架。
  • 不常变的资源(如图片、文档):设置一个合理的强制缓存时间(如几小时或几天)。

4.4 高级:为动态接口生成自定义 ETag

缓存不只是静态资源的专利。对于那些数据变化不频繁的动态接口(比如查询字典、配置项),同样可以利用协商缓存大幅提升性能,降低数据库压力。

Spring Boot提供了ShallowEtagHeaderFilter,可以自动为响应内容生成ETag:

@Configuration
public class ETagConfig {
    /**
     * 自动为所有响应生成浅ETag
     * 基于响应内容的MD5哈希值
     */
    @Bean
    public FilterRegistrationBean shallowEtagHeaderFilter() {
        FilterRegistrationBean filterBean = new FilterRegistrationBean<>();
        filterBean.setFilter(new ShallowEtagHeaderFilter());
        // 只对指定接口生效,避免不必要的计算开销
        filterBean.addUrlPatterns("/api/dict/*", "/api/config/*");
        filterBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return filterBean;
    }
}

配置后效果立竿见影:当请求/api/dict/list时,服务器会计算响应体的ETag。下次请求如果数据没变,ETag匹配,服务器直接返回304,省去了业务逻辑执行和完整数据传输的开销。对于数据量大、调用频繁的只读接口,性能提升非常明显。

4.5 实际开发场景案例

光说不练假把式,我们来看几个真实场景。

场景 1:前端工程化项目的缓存策略

这是现代互联网公司的标准做法:

  1. 前端使用Webpack/Vite等工具打包,为每个JS/CSS文件生成基于内容的哈希文件名(如main.a1b2c3.js)。
  2. 这些带哈希的资源,在Nginx或Spring Boot中配置1年的强制缓存。
  3. HTML文件不设强制缓存,使用no-cache
  4. 当代码更新时,只有变动的文件哈希会改变,用户只需拉取变化的文件,其余资源均从本地缓存读取。更新体验丝滑,带宽节省巨大。
场景 2:字典接口的缓存优化

系统里的字典数据(省份城市、订单状态、性别等)几乎不变,但每个页面都可能调用。给这类接口加上ETag和短暂的强制缓存(比如1小时),QPS提升十倍以上不是梦,还能极大减轻数据库压力。

场景 3:CDN 配合的缓存策略

当引入CDN后,缓存流程变成了三级:浏览器 -> CDN -> 源站。

  1. 用户请求先到CDN节点。
  2. CDN判断自己缓存是否过期(依据就是源站返回的Cache-ControlExpires)。
  3. 如果未过期,CDN直接返回资源给用户(命中)。
  4. 如果过期,CDN会带着If-None-MatchIf-Modified-Since回源站询问。
  5. 源站返回304,CDN刷新自己缓存的过期时间,然后返回缓存给用户。
  6. 源站返回200和新资源,CDN更新缓存并返回给用户。

合理配置缓存头,能保证CDN高效命中,显著降低源站压力,加速全球访问。

五、灵魂拷问:协商缓存真的有人用吗?

这可能是很多后端开发者,尤其是在传统企业或内部系统工作的朋友,心中最大的疑问。

5.1 为什么感觉用不上?

这通常是场景差异导致的错觉:

  • 用户量与性能压力不同:一个几百人用的内部OA系统,服务器资源充裕,不用缓存也跑得飞快。但面对千万日活的C端应用,节省1KB的流量、减少10ms的延迟,乘以用户数就是巨大的成本和体验收益。
  • 技术栈与迭代速度不同:很多老项目采用JSP、Thymeleaf等服务端渲染,页面动态生成,静态资源少且更新慢。而现代前后端分离项目,前端资源独立部署,每周甚至每天迭代,缓存策略直接关乎用户体验和发布效率。
  • 逻辑被基础设施封装了:你没手动写过ETag的逻辑,不代表它不存在。Tomcat、Nginx、Spring Boot在默认情况下就已经为静态资源处理了这些。CDN的核心工作就是缓存。它们都在底层默默工作,开发者无需感知。

5.2 哪些地方在大规模使用?

事实上,协商缓存无处不在:

  • 所有大型网站的前端资源:淘宝、京东、抖音的JS、CSS、图片,绝大部分请求都通过304状态码命中缓存。
  • CDN与对象存储:阿里云OSS、腾讯云COS等服务,默认就会为你存储的文件生成ETag和Last-Modified。
  • 公共API服务:GitHub API、各种地图API、天气API等,广泛使用ETag来避免重复传输相同数据,节省双方资源。

5.3 后端开发到底需要做什么?

对于大多数后端开发者而言,你需要做的不是从零实现缓存逻辑,而是:

  1. 正确配置:根据资源类型,在Nginx、Spring Boot或网关中配置正确的Cache-Control等头部。
  2. 按需开启:对合适的动态接口(如字典、配置)启用ETag过滤。
  3. 会排查问题:当用户反馈“看到的是旧页面”时,能打开开发者工具,看懂网络请求的缓存状态,知道问题是出在强制缓存时间太长,还是协商缓存失效。

六、缓存最佳实践与避坑指南

  • 优先使用强制缓存:对于能确定版本的文件(如带哈希的资源),用max-age,性能最好。
  • 入口文件慎用强制缓存:HTML文件应使用no-cache或较短的max-age,确保用户能获取到最新版本。
  • 不要缓存POST请求:POST通常是非幂等的写操作,缓存它们会导致数据不一致。
  • 更新即改名:更新静态资源时,最好改变其文件名(通过哈希),而不是依赖浏览器协商缓存更新。这是最彻底的缓存失效策略。
  • 善用CDN:结合CDN时,理解publicprivates-maxage等指令的含义。
  • 线上问题,先看缓存:遇到样式没更新、数据不对的问题,首先排查的应该是缓存配置,十有八九是这里出的错。

总结

说到底,协商缓存不是什么高深莫测的黑科技,也不是只存在于面试题里的概念。它是HTTP协议的基础组成部分,是构建高效、可扩展Web应用的基石之一。从浏览器到CDN,从静态资源到动态接口,缓存的思维无处不在。

理解强制缓存与协商缓存的原理,能让你在性能优化时有的放矢,在排查问题时快速定位。记住那句话:在计算机科学中,缓存是最难的问题之一,但一旦用好,它带来的收益也是最高的之一。

来源:https://developer.aliyun.com/article/1737509

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
彻底卸载小龙虾OpenClaw的完整步骤与清理指南

彻底卸载小龙虾OpenClaw的完整步骤与清理指南

彻底卸载小龙虾软件需遵循系统化步骤,因其文件广泛分布于系统目录、用户文件夹及浏览器缓存中。操作流程包括:停止后台服务、清理启动项、卸载npm与Homebrew安装包、删除用户配置文件及临时文件。完成后通过终端验证,若提示命令未找到即表示卸载成功。

时间:2026-05-28 09:46
AI一键生成放假通知模板详细步骤与范文参考

AI一键生成放假通知模板详细步骤与范文参考

放假通知适用对象与安排说明: 公司全体员工: 随着传统佳节的临近,为便于各位同仁提前规划,公司依据国家相关规定并结合运营实际,正式发布本次节假日放假安排。 本次统一休假时段为2023年XX月XX日(星期X)至XX月XX日(星期X),共计X天。XX月XX日(星期X)正常返岗上班。 请各部门同事在假期前

时间:2026-05-28 09:45
AI智能体重构电商营销 OPC中国探索增长新路径

AI智能体重构电商营销 OPC中国探索增长新路径

AI智能体正推动电商竞争向智能密集型转变,通过自动生成内容、智能客服、数据分析与自动化运营显著提升效率。OPC中国探索的“一人公司”与“一人部门”模式,展现了个人借助AI工具完成多岗位工作的未来组织形态。电商竞争的核心将转向AI效能与人机协同能力。

时间:2026-05-28 09:45
AI文件转CAD格式的详细步骤与实用技巧

AI文件转CAD格式的详细步骤与实用技巧

AI文件可通过三种方法转换为CAD格式。一是利用AdobeIllustrator的导出功能,直接保存为DWG或DXF格式。二是借助在线转换工具,上传文件并选择输出格式即可。三是通过第三方软件如AutoCAD或Inkscape进行导入和转换。无论采用何种方式,转换后都应在CAD软件中检查文件的完整性和准确性。

时间:2026-05-28 09:43
Claude官方AI Agent技能编写指南

Claude官方AI Agent技能编写指南

为解决AI执行任务缺乏一致性的问题,Claude提出Skill标准化方案。Skill采用三层渐进式架构,通过YAMLfrontmatter、SKILL md正文和参考资料分层加载,确保精准触发与高效执行。文件结构需严格遵循规范,核心SKILL md文件名大小写敏感。描述需清晰说明功能与触发条件。Skill模式涵盖文档创建、工作流自动化和多工具协调。指令编写应

时间:2026-05-28 09:43
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜
热门教程
更多
  • 游戏攻略
  • 安卓教程
  • 苹果教程
  • 电脑教程