你眼里的「小改动」,是客户眼里的线上故障。
一、一次「小改动」让三个客户同时报错
韩松做一个订阅计费 API,客户以中小 SaaS 为主。某个周二他发了一个「优化」版本:响应里的 created_at 从 Unix 秒改成毫秒,理由是「毫秒更精确」;顺手删掉 legacy_id,因为这个字段在他的控制台里 30 天没有非零值。发布说明只有一句「时间字段精度提升,清理冗余字段」。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
三个客户同时报错。客户 A 用 created_at 做本地缓存键,毫秒让键全部失效,一夜多跑了 40 万次计费查询;客户 B 的数据库列是 int,毫秒值溢出,写入直接失败;客户 C 没用 legacy_id,但它的 ORM 按响应 schema 严格校验,少一个字段就抛异常。三个人都不是重度用户,却都在同一天挂了。
韩松的第一反应是「他们怎么不读 changelog」。这句话暴露了真正的问题:兼容性不由作者定义,而由调用方的解析方式定义。只要有人读过这个字段,它就不算「没人用」;只要类型变过,就一定会有人挂。
「没人用」是观测盲区,不是事实。 你能观测到的只有自己控制台里的展示与聚合,观测不到客户把它写进哪个缓存键、哪个数据库列、哪个 schema 校验器。判断一个字段能不能删,要看调用日志里的字段级读取,而不是看自己的报表。
兼容性没有中间态。 一次变更要么所有调用方都不受影响,要么就按不兼容处理并给出窗口。所谓「大部分客户应该没事」不是判断,是赌。
二、三种版本策略与兼容性的定义
版本策略的选择,本质是决定把不兼容变更的成本放在哪:放在 URL 上、放在请求头里,还是放在账户设置里。
| 策略 | 形态 | 可见性 | 缓存友好 | 迁移成本 | 适用场景 |
|---|---|---|---|---|---|
| URL 版本 | /v1/...、/v2/... | 最高,日志与截图都能看出 | 好,路径即缓存键 | 中,要改 base URL | 公开 API、客户较多 |
| Header 版本 | API-Version: 2026-10-01 | 低,藏在请求头里 | 中,缓存要带 Vary | 低,改一个头即可 | 客户技术能力强、变更频繁 |
| 日期版本 | 账户锁定 2026-10-01 | 中,控制台可见 | 取决于实现 | 最低,改设置即可 | 平台型、按账户灰度 |
单人产品最常见的是 URL 版本,因为便宜、好解释、排障时一眼能看出调用的是哪一代;header 版本省 URL 空间,但排障时容易漏看;日期版本最优雅,却要求每个日期点都保留完整行为快照,维护成本最高。
| 变更 | 是否兼容 | 原因 |
|---|---|---|
| 新增可选请求字段 | 兼容 | 老调用方不传,走默认值 |
| 新增响应字段 | 通常兼容 | 严格 schema 校验的调用方可能报错,需提前公告 |
| 删除响应字段 | 不兼容 | 读取方会拿到 undefined 或 null |
| 改字段类型(秒→毫秒) | 不兼容 | 语义与值域同时改变 |
| 改默认值 | 不兼容 | 不传参的调用方行为被改写 |
| 枚举新增取值 | 通常兼容 | switch 穷举的调用方会落到 default |
| 改错误码 | 不兼容 | 按码分支的调用方会走错分支 |
| 下调速率上限 | 视情况 | 对调用方而言接近不兼容 |
发布前的四问。 有没有调用方读过这个字段?类型或单位变了吗?默认值变了吗?错误码集合变了吗?四问里任何一个答案是「是」,就按破坏性变更处理。schema diff 只能发现结构变化,发现不了「单位从秒变毫秒」这种语义变化。
版本号不是营销号。 /v2 只应该在不兼容变更累积到值得让客户改一次 base URL 时出现;把新增可选字段也叫 v2,会让客户学会忽略版本号,等到真正的破坏性变更时不再当回事。
三、弃用公告的字段与渠道
公告的功能不是通知,是让调用方能在自己的排期里完成迁移。一条合格的公告要能回答五个问题,缺一个就会变成来回邮件。
| 要素 | 写法示例 | 常见错误 |
|---|---|---|
| 弃用什么 | POST /v1/charge 与响应字段 legacy_id | 只说「旧接口」,不给具体路径 |
| 替代是什么 | POST /v2/payments,对应字段 id | 只给新接口,不给字段映射 |
| 何时下线 | 2027-01-31 00:00 UTC | 写「尽快」「下个季度」 |
| 怎么迁移 | 三步:换 URL、改字段、跑回归 | 只给链接,不给步骤 |
| 找谁 | 支持邮箱加一个工单模板 | 只留一个没人看的群 |
提前量的下限见第五节算式;节奏上,首次公告发在 T-90,第二次 T-30,最后一次 T-7,下线当天再发一条「已下线」的收尾通知。只发一次公告,等于没发。
| 渠道 | 覆盖率 | 提前量 | 备注 |
|---|---|---|---|
| 邮件(账户管理员) | 高 | 首次公告即发 | 区分「必读」与「营销」,否则会被归档 |
| 文档横幅与旧版页 | 中 | 全程 | 按版本挂载,不按单篇文章挂 |
| Changelog / RSS | 中 | 全程 | 可订阅,适合技术型客户 |
| 响应头 | 高 | 全程 | 只要发生调用就看得见 |
| 控制台横幅 | 高 | 全程 | 带一个「我已迁移」按钮 |
公告要能被转发。 内容写成客户可以直接贴进自己工单或周报的形式:一段变更摘要、一张字段映射表、一个可执行的迁移步骤。客户的技术负责人需要拿它去说服老板,而不是自己再写一遍。
公告不是一次群发。 大客户要单独发,附上他们自己的调用量与前三个月的字段读取记录;长尾客户用统一邮件即可。区别对待不是偏心,是让最可能受影响的人先动。
四、双版本并行、Sunset 头与下线判据
双版本并行是必要的过渡,但它有成本上限,也有明确的退出条件。
双版本不是「多一套路由」,是两套测试、两套文档、两套监控、两套值班。一个人的产品最多同时维护两个公开版本;出现第三个时,必须先下线一个。成本可以粗算:旧版本维护工时 × 窗口月数 × 时薪。
标准做法是在响应头里标注弃用状态与下线时间,并在文档里给出迁移入口。
Deprecation: @1767225600
Sunset: Wed, 31 Jan 2027 00:00:00 GMT
Link: <https://docs.example.com/migrate/v2>; rel="deprecation"
旧版本调用量要按「调用方 × 端点」拆分,不能只看总量。总量下降可能只是一个大客户迁移了,长尾仍在;只有按调用方看,才知道还差谁。
| 条件 | 判据 | 不满足时 |
|---|---|---|
| 公告满窗 | 公告日距今 ≥ max(90, 2 × 最长发版周期) 天 | 延后下线,不缩短窗口 |
| 调用占比 | 旧版本请求 < 总量 5%,且连续 14 天无新增调用方 | 定向联系剩余调用方 |
| 指南已验证 | 3 个真实客户按指南迁移成功 | 先修指南,再谈下线 |
三个条件必须同时满足,缺一个都不下线。窗口没满就延后,占比没降就定向联系,指南没验证就先修指南——这三件事都比「按计划下线」重要。
灰度与回滚。 新版本先对 1% 调用方开放,观察错误率与延迟;旧版本保持只读兼容一段时间。回滚预案要写清楚「旧版本还能不能接回流量」,不能等出事再想。
文档与 SDK 同步。 版本切换当天,文档选择器、SDK 默认版本、示例代码和错误码索引要同时更新;任何一处滞后,都会把迁移成本转回给客户。
下线不是删除。 旧版本下线后可以保留一段时间的 410 响应与迁移提示,返回体里带新地址;直接 404 会让还在重试的客户端误判成网络错误,反而延长故障时间。
五、行动清单与复算验收
下面八条覆盖「识别变更、发公告、并行、下线」四段,按顺序做,不要跳步。
- 列出当前所有公开版本与各自的调用量
- 把最近 90 天的变更按兼容与不兼容分类
- 给每个破坏性变更写五要素:弃用什么、替代、下线日、迁移步骤、找谁
- 在响应头加
Deprecation与Sunset,验证客户端能读到 - 把公告同步到邮件、文档横幅、changelog 与控制台
- 按 T-90、T-30、T-7 设置三次提醒
- 旧版本调用占比降到 5% 以下,并连续观察 14 天
- 至少让 3 个真实客户按迁移指南走通,再执行下线
验收标准:每个下线动作都能说出窗口天数、公告记录、当前调用占比与已验证的迁移客户数量;四项缺一,不下线。
弃用窗口天数 = 下线日 − 公告日
下限 = max(90, 2 × 客户端最长发版周期)
示例:客户端最长 30 天 → max(90, 60) = 90 天
下线判据:旧版本调用占比 < 5%,且连续 14 天无新增调用方
双版本成本 = 旧版本维护工时 × 窗口月数 × 时薪
示例:6 小时/月 × 3 个月 × 400 元/小时 = 7200 元
使用边界与实操验收
本文讲对外 API 的版本与弃用窗口,不涉及产品本身的发版节奏与回滚流程。后者见 版本节奏与回滚:发布列车、兼容窗口与一次撤回 ,它讲产品发版与回滚,本文讲对外 API 的版本与弃用窗口。契约层面的兼容性检查工具链见 API契约与版本治理:OpenAPI/Protobuf契约管理、兼容性检查与SDK自动生成 ,它讲契约兼容性检查工具链,本文讲公告、并行与下线判据。
文中的 90 天窗口、5% 阈值与 14 天观察期都是教学假设,不是行业标准。真实窗口要按客户的最长发版周期反推:客户半年才发一次版,90 天就不够。
验收以调用日志与公告记录为准,不以「已通知」为准。通知有没有被读到无法证明,旧版本调用量下降可以证明。
关键参考与核验范围
本文的窗口天数、占比阈值与成本数字均为教学演示。核验日期:2026-10-25。
- RFC 8594: The Sunset HTTP Header Field
:定义
Sunset响应头,用于告知资源计划下线的时间。 - RFC 9745: The Deprecation HTTP Response Header Field
:定义
Deprecation响应头与deprecation链接关系,用于在响应中标注弃用状态。
上述两条只用于说明响应头的标准语义,不构成对任何实现或工具的推荐;具体字段格式以 RFC 原文为准。
系列导航
- 上一篇:文档站:五分钟跑通第一个请求与错误自查
- 下一篇:开发者支持:工单分级、状态页与事故沟通
- 回到目录:一个人把 API 做成产品:对外接口、开发者体验与支持
- 相关阅读:版本节奏与回滚:发布列车、兼容窗口与一次撤回 、API契约与版本治理:OpenAPI/Protobuf契约管理、兼容性检查与SDK自动生成
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。