HTTP 缓存
HTTP 缓存是 Web 性能优化的关键一环。通过缓存,可以减少网络请求,加快页面加载速度,并降低服务器负载。HTTP 缓存主要通过一系列请求头和响应头来控制。
缓存控制 (Cache-Control
)
Cache-Control
是 HTTP/1.1 中用于控制缓存行为的主要头部字段,它提供了丰富的指令来定义缓存策略。
max-age=<seconds>
:指定资源能够被缓存的最长时间(以秒为单位)。例如max-age=3600
表示资源在 1 小时内有效。这是控制缓存生命周期的首选方式。no-store
:禁止缓存任何版本的响应。每次请求都必须发送到源服务器,不应在任何缓存中存储。no-cache
:允许缓存,但在使用缓存副本之前,必须向源服务器进行验证(Revalidation),以确保资源没有过期。通常与ETag
或Last-Modified
配合使用。如果资源未更改,服务器返回304 Not Modified
。must-revalidate
:一旦资源过期(例如,超过max-age
),缓存必须在向客户端提供服务前验证其状态。这可以防止在网络断开等特殊情况下使用过期的资源。public
:表明响应可以被任何缓存(包括浏览器、CDN 等共享缓存)缓存。private
:响应只能被单个用户的私有缓存(通常是浏览器缓存)存储,不能被共享缓存(如 CDN)缓存。适用于包含用户个人信息的内容。immutable
:表示响应正文在未来不会改变。当资源被标记为immutable
时,客户端在缓存有效期内不需要发送验证请求。这对于版本化的静态资源(如style.v123.css
)非常有用。
缓存验证
当缓存过期或 Cache-Control
指令要求验证时,客户端会使用条件请求头来询问服务器资源是否发生了变化。
Last-Modified
& If-Modified-Since
Last-Modified
(响应头): 服务器在响应中告知客户端资源的最后修改时间。httpLast-Modified: Wed, 21 Oct 2025 07:28:00 GMT
If-Modified-Since
(请求头): 当缓存过期时,客户端在请求中携带此头部,其值为之前收到的Last-Modified
时间。服务器比较该时间与资源的实际修改时间:- 如果资源未修改,返回
304 Not Modified
,不包含响应正文。 - 如果资源已修改,返回
200 OK
和新的资源内容。
- 如果资源未修改,返回
缺点: Last-Modified
的精度只能到秒,且在分布式系统中,不同服务器上的文件修改时间可能不一致。
ETag
& If-None-Match
为了解决 Last-Modified
的问题,引入了 ETag
(Entity Tag)。
ETag
(响应头): 服务器为资源生成的唯一标识符,通常是文件内容的哈希值或版本号。httpETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match
(请求头): 客户端在验证请求中携带此头部,其值为之前收到的ETag
。服务器比较该ETag
与当前资源的ETag
:- 如果
ETag
匹配,说明资源未变,返回304 Not Modified
。 - 如果
ETag
不匹配,说明资源已更新,返回200 OK
和新的资源。
- 如果
ETag
的优先级高于 Last-Modified
。如果请求中同时包含 If-Modified-Since
和 If-None-Match
,服务器会优先处理 If-None-Match
。
其他重要头部
Expires
: HTTP/1.0 的头部,用于指定缓存的过期日期。httpExpires: Wed, 21 Oct 2025 07:28:00 GMT
由于它依赖于客户端和服务器的时钟同步,
Cache-Control: max-age
在 HTTP/1.1 中被推荐作为替代方案。如果Cache-Control
和Expires
同时存在,Cache-Control
的优先级更高。Age
: 当资源从代理缓存(如 CDN)中获取时,Age
头部表示该资源在代理缓存中已经存放了多长时间(以秒为单位)。浏览器可以用它来判断缓存是否仍然新鲜。Vary
: 用于告知缓存服务器,对于同一个 URL,响应内容可能会根据请求头的不同而有所不同。例如,Vary: Accept-Encoding
表示服务器会根据客户端支持的压缩算法(如gzip
,br
) 返回不同的压缩版本。缓存服务器需要根据Vary
指定的请求头来为同一个 URL 存储多个版本的响应。
缓存决策流程
下面是一个简化的流程图,展示了浏览器如何决定是从缓存加载资源还是向服务器发送请求。
- 检查本地缓存:浏览器发起请求时,首先检查是否存在该资源的本地缓存。
- 无缓存:直接向服务器请求新资源。
- 检查缓存是否过期:如果存在缓存,则检查其是否过期(依据
max-age
或Expires
)。- 未过期:直接使用本地缓存的资源(Fresh)。
- 进行服务器验证:如果缓存已过期(Stale),则检查是否需要验证(依据
no-cache
或must-revalidate
)。- 需要验证:向服务器发送一个条件请求(携带
If-None-Match
/If-Modified-Since
头)。- 服务器返回
304 Not Modified
:表示资源未改变。浏览器更新缓存的元数据(如新的过期时间),然后使用本地缓存的资源。 - 服务器返回
200 OK
:表示资源已更新。浏览器下载新的资源,并用它替换旧的缓存。
- 服务器返回
- 无需验证(例如,没有
must-revalidate
指令):在某些情况下,浏览器可能会直接使用已过期的缓存(例如,在离线模式下)。但在常规在线情况下,通常会重新从服务器获取。
- 需要验证:向服务器发送一个条件请求(携带
最佳实践
不常变的静态资源 (CSS, JS, 图片):
- 使用
Cache-Control: max-age=31536000, immutable
。 - 文件名中加入哈希值(如
main.a1b2c3d4.css
),当文件内容改变时,文件名也会改变,从而绕过缓存。
- 使用
经常变的静态资源 (HTML):
- 使用
Cache-Control: no-cache
。 - 这会强制浏览器每次都向服务器验证,但如果文件未变,服务器只需返回
304
,无需传输整个文件。
- 使用
API 请求:
- 根据业务需求决定。对于实时性要求高的数据,使用
Cache-Control: no-store
。 - 对于可以接受短暂延迟的数据,可以使用
Cache-Control: max-age=<short_duration>, private
,例如max-age=60
。
- 根据业务需求决定。对于实时性要求高的数据,使用
通过合理配置这些缓存策略,可以显著提升网站性能和用户体验。