题干与适用场景
这道题考察后端工程师能否把并发控制落实到 HTTP 协议边界。重点是区分缓存校验与写入保护,说明服务端如何比较实体标签、返回明确状态,以及数据库更新和响应头如何保持一致。
面试官考察什么
- 是否理解 ETag、If-Match、If-None-Match 的不同语义。
- 是否能用强校验器保护写入,避免旧版本覆盖新版本。
- 是否能定义 412、428、409 等冲突和前置条件失败的边界。
- 是否能说明代理缓存、重试、权限和跨副本一致性风险。
回答前需要澄清的问题
先确认资源是整体替换还是部分更新,客户端是否必须带版本,是否允许离线编辑和合并,ETag 是否代表完整表示还是业务版本,以及读写是否经过缓存、负载均衡和多个数据库副本。
30 秒回答框架
读取资源时返回表示对应的 ETag。修改请求必须带 If-Match,服务端在同一事务中比较当前版本并执行带版本条件的更新;不匹配返回 412,不带前置条件则按策略返回 428。成功后生成新 ETag。GET 使用 If-None-Match 可返回 304,但缓存验证不能替代写入保护。
分步骤深入解答
1. 区分缓存验证与写入保护
If-None-Match 适合 GET 缓存验证:表示未变化时返回 304。If-Match 用于要求当前表示匹配,常见于 PUT、PATCH 或 DELETE。不要把“客户端缓存命中”当成“客户端有权覆盖当前资源”,两者的比较方向和失败结果不同。
2. 选择可安全比较的 ETag
写入保护使用强 ETag,确保字节表示或定义的版本完全匹配;弱 ETag 适合语义等价的缓存场景,不应用来允许精确更新。ETag 不应泄露内部递增 ID 或敏感信息,可以由规范化表示、版本号与不可预测盐计算,并在权限过滤后生成。
3. 把比较和更新放在同一边界
服务端读取当前版本、比较 If-Match 并更新资源必须在同一数据库条件写入中完成,例如 UPDATE ... WHERE id = ? AND version = ?。受影响行数为零时返回 412;成功后递增版本并用新版本生成 ETag。不要先在应用内查询,再发起没有条件的写入。
4. 设计失败响应与重试路径
缺少必需前置条件可返回 428,ETag 不匹配返回 412,业务状态冲突可返回 409。响应应包含当前资源或重新读取的提示,但不要替客户端静默覆盖。客户端收到 412 后重新 GET、展示差异或执行明确的字段合并,再带新的 ETag 重试;自动重试必须有次数和幂等边界。
5. 处理缓存、复制和运维细节
生成 ETag 的读取必须来自能看到最新写入的版本,或接受有界的读后写延迟。代理缓存要正确转发 ETag、If-Match 和 If-None-Match,敏感资源使用合适的缓存控制。记录请求 ID、旧新版本、结果和冲突率,便于定位客户端长期使用旧表示或某个副本落后。
高质量示范回答
我会让 GET 返回资源表示和强 ETag,例如由规范化内容与版本生成。PUT、PATCH、DELETE 必须带 If-Match;服务端在数据库中执行带版本条件的更新,受影响行数为零就返回 412,缺少前置条件按接口策略返回 428,成功则递增版本并返回新的 ETag。GET 带 If-None-Match 且标签匹配时返回 304,这只是缓存节省,不能替代写入保护。客户端收到 412 后重新读取、展示差异或执行明确字段合并,再带新标签重试。跨副本读取要保证看到最新版本,代理必须正确转发条件头;通过冲突率、旧版本请求和副本延迟监控协议是否被正确使用。
常见错误
- 只用客户端时间戳比较,忽略时钟偏差和表示差异。
- 用弱 ETag 保护精确写入,导致语义相近但内容不同的更新被接受。
- 先查询版本,再无条件更新,比较与写入之间存在竞态。
- 把 412、409、428 混为一个通用错误,客户端无法决定下一步。
- 让服务端自动覆盖冲突,丢失用户刚刚读取后的修改。
- 只在应用节点生成标签,却没有考虑缓存和数据库副本的可见性。
追问及应对
ETag 可以直接用数据库自增版本吗?
可以把版本作为内部生成输入,但对外应按 HTTP 语义编码并避免暴露敏感业务信息。若表示会因字段筛选或权限不同而变化,标签必须在对应表示边界生成。
PATCH 的两个字段互不冲突,可以自动合并吗?
可以提供明确的字段级合并协议,但仍应校验客户端基于哪个 ETag。合并规则、不可合并字段和审计记录必须公开,不能把“字段不同”当成所有业务冲突都可忽略。
为什么不只返回 409?
412 表示 If-Match 等前置条件失败,428 表示要求客户端先提供前置条件,409 更适合资源当前状态与业务操作冲突。区分它们能让客户端选择重新读取、补充头部或调整业务动作。
经过 CDN 时如何避免旧标签写入?
写请求应到达权威源,缓存只服务读取;响应和请求条件头按规范转发。更新成功后使相关缓存失效或缩短可接受的陈旧窗口,并监控源站版本与边缘标签差异。