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 GMTIf-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。
- 根据业务需求决定。对于实时性要求高的数据,使用
通过合理配置这些缓存策略,可以显著提升网站性能和用户体验。