引言

上一篇我们学习了 HTTP 的报文结构。今天要讲的是 HTTP 中最实用的优化手段——缓存

为什么网页第二次打开比第一次快?为什么浏览器有时显示"从缓存加载"?为什么改完代码刷新页面还是旧内容、需要"强制刷新"?这些都是 HTTP 缓存在起作用。

理解缓存机制,不仅能帮你写出加载更快的网页,还能让你在遇到"缓存问题"时快速定位原因,而不是盲目地"清缓存、重启浏览器"。

第一部分:缓存的分类

HTTP 缓存分为两大类:强缓存协商缓存

缓存类型 是否发请求 状态码 控制字段
强缓存 ❌ 不发 200 (from disk cache) Expires / Cache-Control
协商缓存 ✅ 发 304 Not Modified Last-Modified / ETag

第二部分:强缓存

强缓存 = 在缓存有效期内,直接使用本地缓存,不向服务器发请求

一、Expires(HTTP/1.0)

响应头:
Expires: Mon, 02 Jun 2025 10:00:00 GMT

含义:在 Mon, 02 Jun 2025 10:00:00 GMT 之前,直接使用缓存,不请求服务器。

致命缺陷:Expires 使用绝对时间。如果客户端和服务器时间不同步(比如客户端时间比服务器快 5 分钟),缓存可能提前失效或过期后仍被使用。

二、Cache-Control(HTTP/1.1,推荐)

响应头:
Cache-Control: max-age=3600

含义:从请求时刻算起,缓存有效 3600 秒(1 小时)。这是相对时间,完美解决了 Expires 的时间同步问题。

Cache-Control 常用指令

指令 含义 方向
max-age=秒 缓存有效期(相对时间) 响应
public 任何缓存都可以存储(浏览器+CDN) 响应
private 只能浏览器缓存,不能 CDN 缓存 响应
no-cache 可以缓存,但每次必须验证 请求/响应
no-store 完全不缓存 请求/响应
must-revalidate 过期后必须重新验证 响应

三、Expires 和 Cache-Control 同时存在

Expires: Mon, 02 Jun 2025 10:00:00 GMT
Cache-Control: max-age=3600

Cache-Control 优先级高于 Expires。两个都写是为了兼容老版本客户端。

四、强缓存命中时发生了什么

第三部分:协商缓存

协商缓存 = 缓存过期了(或设置了 no-cache),浏览器向服务器验证"我的缓存还能用吗?"

服务器根据资源的标识判断:

  • 没变 → 返回 304 Not Modified,浏览器继续用缓存

  • 变了 → 返回 200 OK + 新内容

一、Last-Modified / If-Modified-Since(基于时间)

Last-Modified 的缺陷

  1. 秒级精度不够:如果文件在 1 秒内被修改两次,Last-Modified 分辨不出

  2. 内容不变但修改时间变了:文件被重新保存(没改内容),Last-Modified 也变了

  3. 不适用于动态资源:动态生成的页面没有"最后修改时间"

二、ETag / If-None-Match(基于内容,推荐)

ETag 如何生成?

  • 对文件内容做 MD5 或 SHA-1 哈希

  • Nginx 默认用文件修改时间 + 文件大小生成

  • 动态资源可以用业务数据的版本号

ETag 解决了 Last-Modified 的所有缺陷

问题 Last-Modified ETag
秒级精度不够 ✅ 内容变了就变
只改时间不改内容 ❌ 会误判 ✅ 内容不变 ETag 不变
动态资源 ❌ 没有修改时间 ✅ 可自定义生成

三、Last-Modified 和 ETag 同时存在

响应头:
Last-Modified: Mon, 02 Jun 2024 10:00:00 GMT
ETag: "abc123"

ETag 优先级更高。浏览器先验证 ETag,再验证 Last-Modified。两个都写也是为了兼容性。


第四部分:浏览器行为

一、不同操作对缓存的影响

操作 强缓存 协商缓存
地址栏回车、链接跳转 ✅ 生效 ✅ 生效
F5 刷新 ❌ 跳过 ✅ 生效
Ctrl + F5 强制刷新 ❌ 跳过 ❌ 跳过

二、浏览器缓存位置

第五部分:缓存策略实战

一、不同类型的资源如何设置缓存

资源类型 推荐策略 Cache-Control 原因
首页 HTML no-cache max-age=0, must-revalidate 每次验证,确保最新
CSS/JS(带版本号) 强缓存 max-age=31536000, immutable 一年不过期
图片/字体 强缓存 max-age=2592000 30 天
API 接口 no-store 或协商缓存 no-cache 数据实时性高
用户头像 短强缓存 max-age=300 5 分钟

二、为什么 CSS/JS 文件名要带哈希

<!-- 老方式(有缓存问题) -->
<link rel="stylesheet" href="/style.css">

<!-- 现代方式(带版本哈希) -->
<link rel="stylesheet" href="/style.a3f2b1c.css">

第六部分:完整示例

# 服务器响应头配置示例(Nginx)

# HTML:不缓存,每次验证
location / {
    add_header Cache-Control "no-cache, must-revalidate";
}

# 带哈希的静态文件:长期缓存
location ~* \.[a-f0-9]{8}\.(css|js)$ {
    add_header Cache-Control "max-age=31536000, immutable";
}

# 图片:中期缓存
location ~* \.(png|jpg|gif|svg|ico)$ {
    add_header Cache-Control "max-age=2592000";
}

第七部分:面试题

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

A:强缓存命中时不向服务器发请求,直接用本地缓存(200 from cache)。协商缓存每次向服务器发请求验证,304 时用缓存,200 时重新下载。

2. Q:Expires 和 Cache-Control 的区别?

A:Expires 是 HTTP/1.0 的,用绝对时间(服务器时间),客户端时间不准会出问题。Cache-Control 是 HTTP/1.1 的,用相对时间(max-age),优先级更高。

3. Q:no-cache 和 no-store 的区别?

A:no-cache 可以缓存,但每次必须向服务器验证。no-store 完全不缓存。

4. Q:Last-Modified 和 ETag 的区别?

A:Last-Modified 基于时间,秒级精度,时间变了内容不变也会误判。ETag 基于内容哈希,更准确,优先级更高。

5. Q:F5 刷新和 Ctrl+F5 强制刷新的区别?

A:F5 跳过强缓存,走协商缓存(带上 Cache-Control: max-age=0)。Ctrl+F5 跳过所有缓存,重新下载(带上 Cache-Control: no-cache + Pragma: no-cache)。

6. Q:为什么静态资源文件名要带哈希?

A:配合强缓存使用。文件内容变了 → 哈希变了 → 文件名变了 → 浏览器认为是新文件 → 不走缓存直接下载。没变 → 文件名不变 → 走缓存。


总结

一、核心对比

对比 强缓存 协商缓存
发请求 ❌ 不发 ✅ 发
状态码 200 (from cache) 304
控制字段 Cache-Control / Expires ETag / Last-Modified
优先级 先判断 强缓存失效后才走
速度 最快(0ms 网络) 较快(有请求但无响应体)

二、一句话记忆

HTTP 缓存分两级:强缓存用 Cache-Control/Expires 控制有效期,有效期内根本不发请求;过期后用 ETag/Last-Modified 向服务器协商验证,304 就继续用旧的、200 就下载新的。no-cache 不是不缓存、no-store 才是不缓存。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐