Post

HTTP协议--缓存

HTTP协议--缓存

HTTP为什么需要缓存

对于具有重复性的HTTP请求(例如部分GET,HEAD请求),在服务端没有修改数据的情况下,每次请求的结果是一样的,因此可以把[请求-响应]数据缓存在本地,避免访问服务器,提高性能

HTTP缓存有两种实现方法,分别是强制缓存协商缓存

强制缓存

强制缓存通过 Cache-Control 或 Expires 响应头实现。浏览器或客户端在缓存有效期内直接从本地读取资源,不向服务器发起请求。

HTTP 缓存中强制缓存的启用由服务端决定

客户端(如浏览器)仅根据服务端返回的响应头执行缓存策略

  • no-cache:客户端要求服务器验证资源是否过期(强制走协商缓存)。
  • max-age=0:客户端要求缓存立即过期(强制走协商缓存)。
  • no-store:客户端明确禁止缓存(既不强制缓存,也不协商缓存)。

Cache-Control与Expires比较

  1. Cache-Control: max-age=N
    • 计算方式: 过期时间 = 资源获取时间 + max-age(秒)
  2. Expires: <日期>
    • 过期时间 = Expires 头中指定的绝对时间(GMT 格式)。
    • 示例:Expires : Thu, 01 Oct 2023 13:00:00 GMT

Cache-Control 更优

  1. 相对时间不受客户端时钟影响,更可靠。
  2. 支持更多缓存控制指令(如 no-cache, must-revalidate)。
  3. 现代Web开发首选

Expires局限性

  1. 依赖客户端和服务端时钟严格同步,否则缓存时间计算可能错误。
  2. 已被 Cache-Control 取代,仅用于兼容旧系统。

实现过程:

  • 客户端(浏览器)向服务端发送请求
  • 服务端返回资源,在头部加上Cache-Control: max-age=N告诉客户端资源过期时间(相对时间)
  • 客户端再次请求访问服务器中的该资源时,会根据赏赐访问的时间辍+Cache-Control中过期时间与现在时间比较,确认资源是否过期,如果没有,直接使用本地缓存,如果没有,重新请求服务器
  • 服务器再次受到请求后,会更新 Cache-Control

协商缓存

协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。

协商缓存可以使用两种头部实现

第一种:请求头部的If-Modified-Since字段与响应头部的Last_modified字段实现,这两个字段的作用:

  • 响应头部中的 Last-Modified:标示这个响应资源的最后修改时间;
  • 请求头部的If-Modified-Since:当资源过期了而之前的响应头中具有Last-Modified声明,则请求时会将这个值赋给If-Modified-Since.服务段收到后与资源的实际修改时间比较,如果最后修改时间较新(大),说明资源修改过,返回最新资源,否则返回状态码304,浏览器使用本地缓存

第二种:请求头部中的If-None-Match字段与响应头部的Etag字段,这两个字段的作用:

  • 响应头部的Etag:唯一标识响应资源
  • 请求头部中的If-None-Match:当资源过期时,浏览器发现响应头中有Etag字段,则再次向服务器发起请求时,会将请求头If-None-match值设为Etag的值,服务器收到后与资源比较,如果资源没有变化返回状态名304,如果变化则返回200和最新的资源

如果请求同时具有If-Modified_SinceIf-None-Match后者的优先级更高

协商缓存示意图(请求)

协商缓存示意图(响应)

为什么Etag优先级更高

  1. 在没有修改文件内容的情况下文件最后修改时间可能也会改变,者会导致客户端认为文件被改动了,从而重新请求
  2. 可能有些文件是在秒级以内修改的,If-Modified-Since能检查到粒度是秒级的,使用Etag能保证在这种需求下客户端在1秒能刷新多次
  3. 有些服务器不能精确获取文件的最后修改时间

强制缓存和协商缓存的协作

强制缓存优先:

  • 如果资源在强制缓存有效期内(未过期),客户端直接使用本地缓存,不发送请求。
  • 如果资源过期,客户端才会发起请求,并进入协商缓存流程。

协商缓存作为兜底验证

  • 当强制缓存失效后,客户端会在请求头中携带 If-None-Match(对应 ETag)或 If-Modified-Since(对应 Last-Modified)。
  • 如果服务器返回 304 Not Modified,客户端更新缓存有效期(例如重置 max-age),继续使用本地缓存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        客户端发起请求
            │
            ▼
   ┌───────────────────┐
   │ 检查强制缓存是否有效 │
   └─────────┬─────────┘
             │
   ┌─────────▼─────────┐                ┌───────────────┐
   │ 缓存有效?         ├─Yes───────────► 直接使用缓存     │
   └─────────┬─────────┘               └───────────────┘
             │No
             ▼
   ┌───────────────────┐
   │ 发送请求,并携带验证头 │
   │(If-None-Match等)  │
   └─────────┬─────────┘
             │
   ┌─────────▼─────────┐                ┌───────────────┐
   │ 服务器验证资源是否变化 ├─No───────────► 返回304,更新缓存 │
   └─────────┬─────────┘               └───────────────┘
             │Yes
             ▼
   ┌───────────────────┐
   │ 返回200和新资源      │
   └───────────────────┘
This post is licensed under CC BY 4.0 by the author.