304 是什么
304 Not Modified 是条件请求的结果:客户端在请求里带上之前拿到的校验值或修改时间,服务器比对后发现资源没有变化,于是只回一个状态码与少量头部,不传响应体,客户端直接复用本地缓存。它属于重定向类,不是成功类,因为语义是「去用你手里的那份」。正确使用它能省下大量带宽,但前提是客户端确实存了副本,否则收到它反而不知道内容是什么。
基本信息
| 名称 | 304 Not Modified(RFC 9110 §15.4.5;缓存处理见 RFC 9111) |
|---|---|
| 作用 | 协商缓存命中,不传响应体 |
| 触发条件 | 请求带校验值或修改时间,且服务端判定资源未变化 |
| 必备头部 | 应带上校验值、日期与缓存策略等相关头部 |
| 响应体 | 必须没有,有内容属协议违规 |
条件请求示例
条件请求示例
curl -si https://static.example.com/app.js -H 'If-None-Match: v17-abc' # 用校验值发起条件请求
# 命中时返回 304,只有头部,没有响应体
curl -si https://static.example.com/app.js -H 'If-Modified-Since: Mon, 06 Oct 2026 08:00:00 GMT' # 用时间戳发起条件请求
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' https://static.example.com/app.js -H 'If-None-Match: v17-abc' # 命中时下载体积接近零三个校验头对比
| 请求头 | 依赖 | 适用与注意 |
|---|---|---|
| If-None-Match | 响应的校验标签 | 优先使用,精度高,不受时间与节点差异影响 |
| If-Modified-Since | 响应的修改时间 | 精度只到秒,且依赖各节点时间一致 |
| If-Match | 响应的校验标签 | 用于并发控制,不匹配时拒绝写入,避免覆盖他人修改 |
典型使用场景
- 静态资源二次访问:浏览器带上校验值,未变化时只花一个来回
- 接口数据轮询:变化不频繁的列表接口用条件请求降低带宽
- 多节点部署的并发写控制:用条件写入避免后写覆盖先写
- 发布校验:确认新版本资源是否已经生效,看校验值是否变化
条件请求的延迟应该很小,通常只有几毫秒:如果每次校验都要几百毫秒,说明服务端的校验逻辑在现算摘要或查询数据库,这时应该直接延长新鲜期,把校验次数压下来。
多台源站各自生成校验值且算法不一致时,负载均衡之后同一资源的校验值会来回变化,用户的缓存持续失效,表现为「资源每次都要重新下载」。
缓存链路怎么观察
- 开发者工具里状态为未修改且传输体积接近零,说明协商缓存命中
- 如果每次都拿到完整内容,先检查请求有没有带上校验值,很多脚本默认不带
- 命中未修改却仍然很慢,说明服务端校验成本高,应该改为延长新鲜期
- 代理层与浏览器层都有缓存,排障要分别确认到底是哪一层在做校验
两类缓存怎么配合
| 内容类型 | 推荐策略 |
|---|---|
| 带指纹的静态资源 | 优先用强缓存,几乎不需要协商 |
| 入口页面与只读接口 | 用协商缓存,每次验证但通常只花一个来回 |
| 频繁变化的数据 | 缩短新鲜期或直接不缓存,避免用户看到过期内容 |
使用条件请求的四个注意
- 校验值变化即代表内容变化,把它当作发布校验依据很直观
- 把校验值当业务版本号使用会让前后端强耦合,它更适合判断资源一致性
- 时间戳与校验值同时存在时校验值优先,可以避免节点时间差带来的误判
- 给动态接口加条件请求前先确认它的响应足够稳定,否则校验值每次都在变