快速上手 HTTP Cache

2025-01-13 pv

开始系统研究 HTTP Cache,是因为我在开发 裁员追踪器 | Layoffs Tracker🔗 时遇到一个问题:

API 返回的数据是静态的,长时间不更新。如果每次请求都需要访问服务器,不仅效率低下,还会对服务器造成不必要的压力。

所以考虑加一层 Cache。

最简单的实现方式,就是 HTTP Cache。

本以为加几个 HTTP Header 就可以搞定。研究一番后发现,这一块的内容远比想象中丰富。

所以写一篇文章,体系化梳理 HTTP Cache。

重点将放在:产生背景,解决手段,最佳实践。

1. 背景

Cache(缓存)在整个计算机系统里应用广泛。

从最底层的 CPU Cache,到 DNS 解析,CDN,再到耳熟能详的 Redis,本质都是通过缓存提高效率,以空间换时间

HTTP Cache 同样如此。

它能帮助提高网页的加载速度,优化用户体验,而且可以有效地减轻服务器负载

举个例子。

一个静态文件,长期不被修改,那么每次刷新都再次请求是不必要的。速度慢,网络开销大,如果文件尺寸较大,还会给服务器带来更多压力。

HTTP Cache 很好地解决了这个问题。将第一次访问的内容缓存在本地,后续获取资源时便可避开网络请求,直接从本地获取。

2. 解决方案

HTTP Cache 主要有两种:

  • 强制缓存,客户端直接使用本地缓存
  • 协商缓存,客户端向服务器发送请求,由服务器决定是否使用缓存

2.1 强制缓存

强制缓存的关键头部有两个:

  • Expires(不推荐),指定过期的时间点,是一个绝对时间,例如:Expires: Wed, 15 Jan 2025 20:00:00 GMT

  • Cache-Control(推荐),使用相对时间,控制手段更丰富。

    常用指令有:

    • public: 表示资源可以被随意缓存(共享),例如代理服务器
    • private: 表示资源只允许用户终端缓存(私有),代理服务器不可以缓存
    • no-store: 表示完全不允许缓存
    • no-cache: 可以缓存,但使用前要向服务器验证是否过期
    • must-revalidate: 缓存不过期的话可以继续使用,否则就要向服务器重新请求
    • max-age=3600: 表示资源有效期为 3600 秒
    • s-maxage=3600:覆盖 max-ageExpires,但仅用于共享缓存,私有缓存会忽略

我在 裁员追踪器 | Layoffs Tracker🔗 用到的配置如下:缓存时间为 60 秒,而且可以被共享。

Cache-Control 是一个通用字段,服务端和客户端都可以使用

服务端用它实现在客户端上的缓存策略,反过来,客户端可以重新设置,决定如何使用当前缓存。

例如,浏览器“刷新”按钮,会在请求里会设置 Cache-Control: max-age=0,绕过本地缓存,从服务器获取最新内容。

2.2 协商缓存

相比强制缓存这种单边决策,协商缓存更加复杂:需要客户端和服务端的双向配合

关键头部有两部分,请求头和响应头。

  • 请求头:
    • If-Modified-Since: 上次修改时间,用于配合 Last-Modified
    • If-None-Match: 本地的缓存版本标识(ETag)
  • 响应头:
    • Last-Modified,最后一次修改时间,用于比较资源是否发生修改,精度不如 ETag
    • ETag,资源的唯一标识,如果原始资源变更,ETag 必须重新生成。比较 ETag 可以更加高效、准确地判断变化的发生

具体工作流程如下:

  1. 客户端在第一次请求时,会收到 Last-ModifiedETag 字段
  2. 客户端再次请求时,附带 If-Modified-SinceIf-None-Match 字段
  3. 服务端根据请求字段,判断缓存是否有效:
    • 有效,返回 304 Not Modified,客户端直接使用缓存
    • 无效,返回新的资源和状态码 200 OK,当然 Last-ModifiedETag 同样会返回

例如,访问某个 .js 资源:

2.3 优先级

强制缓存和协商缓存可以同时存在。

同时存在时,会优先判断强制缓存是否有效,无效则进一步使用协商缓存。

3. 最佳实践

不同场景适合不同的缓存策略。

对于静态文件,如 CSS、JS 和图像等,通过设置较长的缓存时间,并将版本信息填充到文件名里,避免因缓存时间过久导致未能及时获取最新内容。

例如:

  • Cache-Control: max-age=31536000
  • 文件名示例:style.v2.cssapp.123abc.js

对于动态资源,因为变动频率高,协商缓存更合适:

  • ETagLast-Modified 提供内容变更验证机制
  • 响应头设置 Cache-Control: no-cache

像那些涉及登陆等隐私相关的,建议禁止缓存:

  • 响应头设置 Cache-Control: no-store, private

4. 总结

在优化性能方面,缓存是个简单,但是强大的工具。

以空间换时间,在准确性和效率中找到一种平衡。

组合使用 HTTP Cache,优化加载和网络访问,基本算是前端开发者的必修课。

毕竟,在复杂多变的网络环境中,缓存多少提供了一些确定性。

(完)

参考

  1. HTTP 缓存 - HTTP | MDN🔗
  2. Prevent unnecessary network requests with the HTTP Cache | Articles | web.dev🔗
  3. Cache-Control - HTTP | MDN🔗
在 GitHub 上编辑本页面

最后更新于: 2025-12-16T08:10:54+08:00