代表性面试主题

后端面试题:如何把 API 从 200 JSON 安全迁移到 204?

后端中等
Offer.cc 编辑团队发布 更新

题干

一个写接口长期返回 200 和 JSON 成功对象。为了减少无用响应体,团队准备改为 204 No Content。请设计一套兼容现有客户端的迁移方案。

1. 题目与适用场景

PATCH /profiles/42 已经运行多年。成功时服务端返回 200 OK,响应体固定为 { "success": true }。Web、移动端、第三方软件开发工具包(Software Development Kit,SDK)和自动化任务都在调用它。团队发现这个 JSON 没有业务信息,准备改为 204 No Content

难点不在修改状态码。旧客户端可能对每个 2xx 响应都调用 response.json(),网关可能按响应体提取字段,监控也可能把 200 当成唯一成功状态。迁移方案必须让新旧客户端同时工作,并能在异常扩大前立即退回原契约。

2. 面试官考察点

  • 能否把状态码变化看成响应契约迁移,而非一行服务端代码。
  • 能否列出浏览器、移动端、SDK、代理、监控和重试器等真实消费者。
  • 能否设计版本化或 Prefer 协商,让 200 与 204 在迁移期共存。
  • 能否给出灰度指标、停止条件和无需客户端回退的服务端回滚路径。
  • 是否知道 204 到响应头结束即终止,不能携带内容或 trailer。

3. 迁移前要确认什么

  1. 哪些客户端会无条件解析 JSON,哪些只检查 response.ok 或 2xx?
  2. 成功对象是否真的没人读取,包括日志采集、网关脚本和生成的 SDK?
  3. 请求是否可能被客户端自动重试;解析失败会不会把一次成功写入误判为失败并重复提交?
  4. 团队能否升级所有客户端,还是必须长期保留双契约?
  5. 响应中的 ETag、速率限制和追踪头是否仍需保留?

4. 30 秒回答框架

我会先建立消费者清单,用契约测试找出所有依赖 200 JSON 的代码。迁移期保留 200 为默认,新客户端通过新版本或 Prefer: return=minimal 明确选择最小响应;服务端采用后返回 204,并用 Preference-Applied 表明协商结果。随后按内部调用、低风险客户端和主要流量分批放量,监控解析异常、重复写入、重试率和各客户端成功率。回滚只需要关闭服务端 204 分支,因为序列化 200 JSON 的能力在迁移结束前一直保留。

5. 分阶段迁移方案

第一步:建立客户端能力清单

按调用方记录负责人、版本、请求库、成功判定、响应解析和重试策略。重点搜索 response.json()、固定 status === 200、生成 SDK 的返回类型,以及网关对 body.success 的读取。对无法识别版本的调用方,先维持 200;没有能力证明,就不进入 204 灰度。

第二步:把客户端改成兼容两种成功契约

客户端先发布兼容版本:接受约定的 2xx 状态,在读取正文前检查 204 或正文长度,并把业务成功与 JSON 解析分开。契约测试同时喂入 200 + JSON204 + 空正文。这一步必须先于服务端切换,否则服务端一次成功写入可能在客户端表现为解析失败,进而触发重复请求。

第三步:选择双契约方式

如果这是明确的破坏性版本升级,可以让新 API 版本固定返回 204,旧版本继续返回 200。若路径和语义都要保持,可以采用 RFC 7240 的偏好协商:兼容客户端发送 Prefer: return=minimal,服务端采用后返回 204,并返回 Preference-Applied: return=minimal;需要资源表示的调用方发送 Prefer: return=representation 或继续使用默认 200。若响应可能被缓存,响应还要正确声明 Vary: Prefer

第四步:按客户端而非随机请求灰度

先开放测试环境和内部调用,再按已确认兼容的客户端版本扩大范围。灰度单元应稳定落在客户端或账号级别,避免同一个客户端一会儿收到 200、一会儿收到 204。每一档放量都要观察完整业务周期,再决定是否继续,而不是只看 HTTP 成功率。

第五步:监控协议变化带来的真实故障

服务端按客户端版本记录 200、204、5xx 和重试次数;客户端上报空正文解析异常、成功请求后的错误提示和重复提交。业务侧核对写入成功数与请求重试数,特别关注非幂等操作。告警应能定位到客户端版本和发布批次,单看总体 2xx 会把兼容问题藏起来。

第六步:准备即时回滚和最终收口

迁移期保留原 JSON 序列化路径,由服务端开关控制 204。触发停止条件后,统一恢复 200;已经兼容两种响应的新客户端无需回退。等所有受支持客户端跨过最低兼容版本、旧流量降为零并完成一个稳定观察周期后,再决定是否删除旧路径。删除前重新跑 SDK、代理和契约测试。

6. 高质量示范回答

我会把这次改动当成响应契约迁移。先盘点所有消费者,找出无条件解析 JSON、固定判断 200 或依赖 body.success 的代码,并先发布能同时处理 200 JSON 与 204 空正文的客户端。服务端暂时保留 200 默认;新客户端通过 API 版本或 Prefer: return=minimal 选择 204,服务端用 Preference-Applied 确认。灰度按客户端版本推进,重点看解析异常、重复写入和重试,而不只看 2xx。原 200 序列化路径在迁移期保留,一旦超过停止阈值就关闭 204 分支。确认所有受支持客户端完成升级后,才移除旧契约。

7. 常见错误

  • 直接把 200 改成 204 → 旧客户端解析空正文失败 → 先发布双兼容客户端,再启用新响应。
  • 只检查 HTTP 错误率 → 204 仍是成功状态,解析异常不会进入服务端 5xx → 增加客户端解析、重试和重复写入指标。
  • 按请求随机灰度 → 同一客户端得到不稳定契约 → 按客户端版本或稳定主体分组。
  • 使用 Prefer 却不反馈是否采用 → 客户端无法确认协商结果 → 返回 Preference-Applied,并定义服务端忽略偏好时的默认行为。
  • 切换后立即删除 JSON 生成逻辑 → 出现问题时无法快速恢复 → 等迁移窗口关闭后再清理旧路径。
  • 忽略自动重试 → 成功写入被解析错误伪装成失败 → 检查幂等键、重试器和重复提交指标。

8. 追问及应对

追问一:为什么不能一次性切换所有客户端?

服务端只知道请求成功,无法保证每个客户端都按 204 处理空正文。只要还有一个旧版本无条件解析 JSON,一次性切换就会把协议成功变成用户可见失败。先做客户端兼容,再按已知版本切流,故障边界才清楚。

追问二:版本化和 Prefer 应该怎么选?

新版本适合长期固定的新契约,理解成本低,但需要维护版本生命周期。Prefer 适合同一操作允许“返回表示”和“最小响应”两种合法结果的场景;服务端可以忽略偏好,因此客户端必须定义默认处理,并读取 Preference-Applied。两者都比隐藏式按 User-Agent 猜测可靠。

追问三:204 还能保留哪些信息?

可以保留 ETag、追踪标识和速率限制等响应头;不能发送消息内容或 trailer。客户端也不能把“没有 JSON”理解为“没有元数据”,应分别读取所需响应头。

追问四:什么情况下可以删除 200 兼容路径?

至少满足三项:所有受支持客户端都已发布双兼容版本;观测中没有旧版本流量;SDK、代理、自动化任务和回滚演练全部通过。只看应用商店的新版本发布完成还不够,因为用户可能长期停留在旧版本。

公开来源

同类题目