废弃策略与迁移
基于 HTTP/REST 工程实践 · RFC 8594 (Sunset) · 核于 2026-08
速查
- 废弃(Deprecation)≠ 删除:旧版本/字段不能直接删,要先标记废弃给迁移窗口,到期才下线。直接删会让客户端崩溃(违反 API 稳定性承诺)。
- 废弃通知机制:①**
Deprecation头**(RFC 并非标准但广泛用,Deprecation: true或日期);②**Sunset头**(RFC 8594,Sunset: Wed, 11 Nov 2026 00:00:00 GMT明确退役日期);③响应体deprecation字段;④文档/OpenAPI spec 标记;⑤GraphQL@deprecated指令(schema 层,客户端 IDE 显示删除线)。 - Sunset 头(RFC 8594):HTTP 响应头,明确告诉客户端「这个资源/版本将在何时退役」。客户端可据此主动迁移,而不是某天突然收到 410。是废弃通知的标准做法。
- 迁移窗口(Migration Window):从标记废弃到真正下线的时间(通常 6-12 个月,长尾场景更长)。窗口期新旧版本并存,客户端有时间迁移。窗口长度取决于客户端数量和迁移难度。
- 向后兼容原则(减少升版本频率):①只加字段不减;②只放宽不收紧;③新增可选参数;④改实现不改契约;⑤想改名时「保留旧字段 + 加新字段」给过渡期。
- 替代方案要清晰:废弃通知必须说明「用什么替代」「怎么迁移」——文档链接、新字段名、代码示例。光说「废弃了」不说怎么迁移是不负责任的。
- 下线(Sunset/Retirement):到期后旧版本返回 410 Gone(区别于 404,明确「永久移除别再试」)+ 升级指引 body。监控旧版本流量,接近 0 才下线;有长尾客户端要再延长窗口或保留最小适配。
一、废弃的生命周期
一个 API 版本/字段的完整生命周期:
活跃(Active)
↓ 发生破坏性变更需求
标记废弃(Deprecated) ← 新版本上线,旧版本标记废弃,给迁移窗口
↓ 持续监控 + 客户端迁移(6-12 个月)
退役(Sunset/Retired) ← 旧版本返回 410 Gone,正式下线- 标记废弃不是立即删除:废弃后旧版本仍正常工作(客户端不挂),只是开始倒计时。
- 退役(Sunset)才真正下线:到期后旧版本不再服务,返回 410 Gone。
- 窗口期监控:持续监控旧版本流量,若仍有大量客户端未迁移,要延长窗口或主动联系(对 B2B API)。
二、废弃通知机制
2.1 Sunset 头(RFC 8594)—— 标准
http
HTTP/1.1 200 OK
Sunset: Wed, 11 Nov 2026 00:00:00 GMT
Deprecation: true
Link: <https://api.example.com/v2/users>; rel="successor-version"- Sunset:RFC 8594 定义的标准头,明确「何时退役」(HTTP 日期格式)。客户端可据此规划迁移。
- Deprecation:标记「已废弃」(虽未成为正式 RFC 但广泛使用)。
- Link 头 rel="successor-version":指向新版本,客户端知道迁移到哪。
2.2 响应体字段
json
{
"data": {...},
"meta": {
"deprecation": "此端点已废弃,将于 2026-11-11 下线,请迁移到 /v2/users",
"sunset": "2026-11-11T00:00:00Z",
"successor": "https://api.example.com/v2/users"
}
}适合需要人类可读说明的场景。
2.3 文档与 spec 标记
- OpenAPI:用
deprecated: true标记废弃的 path/operation/field,Swagger UI 显示删除线。 - GraphQL:用
@deprecated(reason: "...")指令标记废弃字段,客户端 IDE(如 Apollo)显示删除线和废弃原因。
2.4 监控与告警
- 日志记录「使用了废弃端点」的客户端,主动联系(对 B2B)或在响应里给个性化迁移提示。
- 设置废弃端点的调用量告警,流量未下降时延期下线。
三、迁移窗口设计
3.1 窗口长度
| 场景 | 建议窗口 | 原因 |
|---|---|---|
| 内部 API(团队可控) | 1-3 个月 | 客户端是内部团队,可协调 |
| B2B API(企业客户) | 6-12 个月 | 企业客户迭代慢,有大客户绑定 |
| 公共 API(海量第三方) | 12-24 个月 | 长尾客户端多,迁移慢 |
| 移动端 API(App) | 6-12 个月 | 旧版 App 用户升级慢,需保留兼容 |
- 过早下线的代价:客户端崩溃、信任流失、口碑下降。
- 过晚下线的代价:维护成本(多版本共存)、技术债累积。
- 平衡:基于客户端迁移进度动态调整(监控流量)。
3.2 帮助客户端迁移
- 详细的迁移文档:变更说明、新旧字段映射、代码示例(curl/SDK 多语言)。
- 新旧字段并存过渡:想改名(email → emailAddress)时,一段时间内同时返回两个字段(旧字段标记 @deprecated),给客户端迁移期,而非直接改。
- 渐进收紧:先废弃警告(响应仍正常),再限速(废弃端点限流),最后下线(410)——给客户端「痛感」促进迁移。
- 沙盒预览:新版本先在 sandbox 环境发布,客户端测试通过后再上线生产。
四、向后兼容原则(减少升版本)
减少废弃与升版本频率的根本是尽量向后兼容:
| 原则 | 做法 | 示例 |
|---|---|---|
| 只加不减 | 新增字段安全,删除破坏 | 加 avatar 字段,不动旧的 |
| 只放宽不收紧 | 必填→可选安全,可选→必填破坏 | email 从必填改可选 |
| 新增可选参数 | 加可选参数安全 | 加 ?fields= 可选 |
| 改实现不改契约 | 优化内部不影响客户端 | 换数据库、加缓存 |
| 保留旧 + 加新 | 想改名时双字段过渡 | 旧 email + 新 emailAddress 并存 |
- 为何重要:每次升 MAJOR 版本(破坏性变更)都增加维护成本(多版本共存)和客户端迁移成本。尽量向后兼容可把这些成本降到最低。
- 必须破坏时果断升版本:别试图「偷偷改」(改契约不升版本)——这比显式升版本更糟,客户端某天突然崩溃却不知原因。
五、下线(Sunset)的执行
到期退役旧版本:
http
HTTP/1.1 410 Gone
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/version-retired",
"title": "API v1 has been retired",
"status": 410,
"detail": "v1 已于 2026-11-11 下线,请迁移到 v2",
"instance": "https://api.example.com/migration-guide"
}- 410 Gone(区别于 404):明确「永久移除,别再请求」,客户端应停止重试并迁移。404 是「不知道有没有」,410 是「曾经有现在永久没了」。
- 退役前确认流量:监控旧版本调用量,接近 0 才下线;有长尾流量要分析(是僵尸客户端还是重要客户未迁移)。
- 保留最小适配(极端情况):对极重要的长尾客户(如大企业),可保留一个最小适配层(
/v1/内部转换到/v2/),而非完全下线。
交互演示
本叶无专门可视化,废弃与迁移建议结合实际项目(如观察 GitHub API 的废弃公告与 Sunset 头)体会。
下一步
版本控制的策略与生命周期到此讲完。下一步可深入 REST API(版本控制的载体)、GraphQL API(靠 @deprecated 几乎不需版本控制)与 OpenAPI 规范(用 deprecated: true 在 spec 层标记废弃)。