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

简介: 本文详解HTTP缓存机制,聚焦强制缓存(Cache-Control/Expires)与协商缓存(ETag/Last-Modified)的原理、区别及实战应用。涵盖F5与Ctrl+F5刷新差异、Spring Boot零代码配置、CDN协同策略,并澄清“协商缓存是否真用”的常见误解。助力前端与后端开发者精准掌控缓存,提升性能与体验。

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

本文首发于个人技术博客,转载请注明出处

你是否遇到过这些问题:

  • 前端更新了代码,用户刷新浏览器还是看到旧版本?
  • 明明服务器资源没变,每次刷新还是要重新下载几十 MB 的 JS?
  • F5 刷新和 Ctrl+F5 强制刷新到底有什么区别?

这些问题的答案,都藏在 HTTP 缓存机制里。而协商缓存作为 HTTP 缓存体系中最灵活、最常用的一环,既是面试高频考点,也是解决线上性能问题的关键。

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

HTTP 缓存分为强制缓存协商缓存两层,它们是递进关系:先判断强制缓存是否命中,命中则直接使用本地缓存;未命中才会进入协商缓存流程。

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

强制缓存的核心是:只要浏览器判断缓存没过期,就直接使用本地资源,完全不发请求到服务器

它通过两个响应头实现:

  • Cache-Control: max-age=86400:相对时间,从响应返回时刻算起,86400 秒(1 天)内有效
  • Expires: Wed, 28 May 2026 10:00:00 GMT:绝对时间,在这个时间点之前有效

核心区别Cache-Control不依赖客户端本地时间,通过计算「服务器响应时间 + max-age」得到过期时间,稳定性远高于Expires。同时存在时,Cache-Control优先级更高。

状态码200 OK (from disk cache)200 OK (from memory cache)

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

强制缓存有一个致命问题:过期后不管资源有没有变化,都要重新下载完整资源

协商缓存就是为了解决这个问题:

强制缓存过期后,浏览器不直接下载资源,而是携带缓存标识发请求给服务器。

服务器判断资源是否更新:

  • 没更新:返回304 Not Modified,告诉浏览器继续用本地缓存
  • 更新了:返回200 OK和完整的新资源

这就是 "协商" 的含义:由服务器最终决定是否使用缓存

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

协商缓存有两种独立的实现方式,可以单独使用,也可以同时使用。

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

缺点

  • 精度只有秒级,1 秒内多次修改无法识别
  • 文件内容没变但修改时间变了(比如重新保存),会导致误判
  • 部分服务器无法准确获取文件修改时间

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

为了解决Last-Modified的缺点,HTTP/1.1 引入了ETag机制:

  1. 第一次请求资源时,服务器根据资源内容计算出一个唯一哈希值,在响应头中返回:

    ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
    
  2. 强制缓存过期后,浏览器再次请求时,会在请求头中携带:

    If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
    
  3. 服务器重新计算当前资源的 ETag,和请求头中的值对比:

    • 如果相等:返回304 Not Modified
    • 如果不等:返回200 OK和新资源,同时更新ETag

ETag 的两种类型

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

2.3 优先级与对比

当响应头中同时存在ETagLast-Modified时:

  • ETag优先级绝对高于Last-Modified
  • 服务器会先判断 ETag 是否变化,只有 ETag 不变时,才会去判断 Last-Modified

2.4 你不知道的 304 响应细节

很多人以为 304 只是一个状态码,其实它有几个非常重要的特性:

  1. 304 响应只返回响应头,不返回响应体:这是它节省带宽的核心原因,一个 304 响应通常只有几百字节
  2. 304 响应会更新本地缓存的过期时间:如果服务器返回了新的Cache-ControlExpires,浏览器会用新值更新缓存
  3. 304 响应不会更新 ETag 和 Last-Modified:只有返回 200 时才会更新这两个标识

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

我们只需要通过简单的配置,就能开启强制缓存,进一步提升性能:

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 进阶:自定义静态资源缓存规则

不同类型的静态资源,缓存策略应该不同。我们可以通过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):设置超长强制缓存,更新时改文件名即可
  • HTML 文件:设置no-cache,每次都走协商缓存,保证用户能看到最新版本
  • 不经常变化的资源(如接口文档、图片):设置适中的强制缓存时间

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

对于不经常变化的动态接口,我们也可以手动实现协商缓存,大幅提升接口性能。

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

@Configuration
public class ETagConfig {
   

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

效果

  • 访问/api/dict/list接口时,服务器会自动生成 ETag
  • 再次请求时,如果接口返回的数据没变,服务器会直接返回 304
  • 对于数据量大、变化频率低的接口,性能提升非常明显

4.5 实际开发场景案例

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

这是目前互联网公司最常用的方案:

  1. 前端打包时,给所有 JS、CSS、图片文件名加上内容哈希值
  2. 这些带哈希值的资源,设置 1 年强制缓存
  3. HTML 文件不设置强制缓存,每次都走协商缓存
  4. 更新时,只有修改过的文件哈希值会变,用户只会下载变化的资源

场景 2:字典接口的缓存优化

系统中的字典接口(如性别、地区、状态)通常变化频率极低,但调用量极大。

  • 配置ShallowEtagHeaderFilter为字典接口生成 ETag
  • 设置Cache-Control: max-age=3600,强制缓存 1 小时
  • 这样既保证了数据的实时性,又能将接口 QPS 提升 10 倍以上

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

当使用 CDN 时,缓存流程变成:

  1. 用户请求 CDN 节点
  2. CDN 节点判断自己的缓存是否过期
  3. 未过期:直接返回给用户
  4. 过期:CDN 节点携带 ETag 和 Last-Modified 回源到服务器
  5. 服务器返回 304:CDN 更新自己的缓存过期时间,返回给用户
  6. 服务器返回 200:CDN 更新缓存,返回新资源给用户

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

这一开始也是是我最最易或的问题,也是很多人对缓存最大的误解。

5.1 为什么我在国企三年没用到?

这完全是场景差异导致的,和技术本身的实用性无关:

  1. 用户量和性能要求不同:国企内部系统通常只有几十到几百个用户,服务器压力极小,即使所有资源都不缓存也能跑通。而互联网公司的系统要面对几百万甚至几千万用户,每节省 1KB 带宽、每减少 100ms 延迟,都能带来巨大的成本节约。
  2. 技术栈和迭代频率不同:很多国企老项目还是 JSP、Thymeleaf 这种服务端渲染技术,静态资源极少,且迭代周期极长,几个月甚至几年才更新一次。而互联网公司的前端项目每周甚至每天都要更新,缓存策略直接影响用户体验。
  3. 缓存逻辑被自动处理了:没写过不代表它不存在。Nginx、Tomcat、Spring Boot 默认就会为静态资源生成 ETag 和 Last-Modified,CDN 的核心就是缓存。这些逻辑都在底层默默工作,后端开发完全感知不到。

5.2 哪些地方在大规模使用协商缓存?

  • 所有互联网公司的前端项目:淘宝、京东、抖音这些网站,90% 以上的静态资源都在使用协商缓存
  • 所有 CDN 和对象存储服务:阿里云 OSS、腾讯云 COS 默认就会为所有文件生成 ETag 和 Last-Modified
  • 公共 API 和开放平台:GitHub API、高德地图 API 等都大量使用协商缓存来节省服务器资源和 API 调用次数

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

绝大多数情况下,你不需要手动实现协商缓存的逻辑,框架和中间件已经帮你做好了。你只需要:

  1. 正确配置Cache-Control头,明确不同资源的缓存策略
  2. 对于数据量大、变化频率低的接口,开启 ETag 生成
  3. 排查线上缓存问题时,能看懂缓存头,知道为什么用户看到的是旧资源

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

  1. 优先使用强制缓存:强制缓存不需要发请求,性能远高于协商缓存
  2. 带哈希值的资源用超长强制缓存:更新时改文件名即可,永远不会有缓存问题
  3. HTML 文件永远用 no-cache:保证用户能看到最新版本
  4. 不要给 POST 接口设置缓存:POST 接口通常是写操作,缓存会导致数据不一致
  5. 更新资源时一定要改文件名:不要依赖协商缓存来更新资源,用户可能会看到旧版本
  6. 线上问题排查时,先看缓存头:90% 的缓存问题都是因为缓存头配置错误导致的

总结

协商缓存不是什么高大上的新技术,也不是只存在于面试中的八股文。它是 HTTP 协议的基础组件,是现代 Web 架构中无处不在的性能优化手段。

缓存确实在互联网的每一个角落默默工作着。理解它的原理,能让你在遇到缓存问题时不再抓瞎,也能让你设计出性能更好的系统。

最后记住一句话:缓存是计算机科学中最难的问题之一,也是收益最高的问题之一

目录
相关文章
|
5天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
2696 9
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
13天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3451 12
|
16天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3529 25
|
9天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
2666 6
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
7天前
|
人工智能 自然语言处理 供应链
|
7天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全+三种模式+记忆体系+实战工作流完整手册
Claude Code 是当前最流行的终端级 AI 编程助手,能够直接在命令行中完成代码生成、项目理解、文件修改、命令执行、错误修复等全流程开发工作。它不依赖图形界面、不占用额外资源,却能深度理解项目结构,自动生成规范代码,大幅提升研发效率。
1227 3
|
28天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23611 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」

热门文章

最新文章