条件请求与 ETag 是什么
条件请求让客户端把已知的版本信息带在请求里,服务端据此决定是返回完整内容还是只回一个状态码。读取场景用 If-None-Match 携带 ETag,内容未变时返回 304,省下带宽;写入场景用 If-Match 携带 ETag 做乐观锁,版本不匹配就返回 412,从而避免两个编辑者互相覆盖——这种丢失更新在协同编辑与配置管理里非常常见。ETag 通常取内容哈希或版本号,多节点部署时要保证同一版本在各节点生成结果一致。
基本信息
| 读取缓存 | If-None-Match + ETag → 未变化返回 304 |
|---|---|
| 写入并发控制 | If-Match + ETag → 版本不符返回 412 |
| 时间戳方式 | Last-Modified + If-Modified-Since,精度到秒,适合静态资源 |
| 弱校验 | ETag 加 W/ 前缀表示语义等价即可,不适合做并发控制 |
| 多节点要求 | 同一资源版本的 ETag 生成规则在各节点一致 |
典型使用场景
- 列表与详情缓存:内容未变时返回 304,降低带宽与服务端渲染开销
- 协同编辑:两个用户同时编辑同一份配置,后提交者得到 412 而不是静默覆盖
- 配置管理:变更前确认版本,防止覆盖他人刚提交的修改
- 对象存储上传:校验对象未被他人替换过再写入
- CDN 与网关优化:静态资源用版本标识控制缓存有效期
- 幂等重放保护:重复提交同一版本请求时可被识别
交互流程
交互流程
GET /docs/1
→ 200 OK, ETag: "v7" # 服务端返回当前版本标识
GET /docs/1 If-None-Match: "v7"
→ 304 Not Modified # 内容未变,客户端直接用本地副本
PUT /docs/1 If-Match: "v7" # 写入前声明自己基于的版本
→ 200 OK # 版本仍为 v7,写入成功
→ 412 Precondition Failed # 已被他人改为 v8,提示重新拉取合并
# 注意:412 不是错误而是正常并发路径,客户端必须有合并或重试逻辑关键点与常见误用
| 要点 | 正确做法 | 常见误用 |
|---|---|---|
| ETag 粒度 | 按资源整体版本生成 | 按字段生成,导致缓存频繁失效 |
| 一致性 | 多节点使用同一内容哈希或版本号 | 各节点用本地时间戳,缓存不断失效 |
| 写入校验 | 使用强校验并真实比较 | 忽略 If-Match 或使用弱校验,丢更新照旧发生 |
| 状态码 | 版本不符返回 412 或 409 | 统一返回 400,客户端无法区分冲突与参数错误 |
| 性能 | 内容大时用版本号代替哈希计算 | 每次请求都全量计算哈希,成为瓶颈 |
ETag 的生成规则要写进接口规范:多节点用内容哈希或版本号时,必须保证同一版本在不同节点得到相同结果,否则缓存命中率会异常偏低。
412 属于正常并发路径,不是系统故障:客户端应当有拉取最新版本、重新合并与提示用户的流程,而不是把报错直接抛给使用者。