半年前公告废弃的接口,今天还有每天两万次调用。公告发过、邮件发过、文档上标了红字、季度会上也提过——唯一没发生的是"它真的停了"。这不是沟通问题,是**"宣布废弃"和"执行废弃"被当成了同一件事**。
宣布是一种表态,把责任从"没有意识到"切换到"知道但还没做"。执行是一串带顺序的动作,需要有人拿着扳手站在系统前面:观测调用、催办到人、逐级加压、最后删代码。前者花一小时,后者通常要一个季度,而且期间一定会有反复。
1. 执行不下去的三类原因
| 原因 | 表现 | 判据信号 | 对策 |
|---|---|---|---|
| 没人负责 | 公告有,负责人没有 | 台账里查不到单条的 owner 与到期日 | 每条废弃项一个负责人 + 一个到期日 |
| 调用方不知道 | 通告发了,回执没有 | 拿不出调用方名单与联系人 | 先出名单,再要回执,再给迁移指引 |
| 没有替代路径 | 替代能力缺失,或迁移收益低于成本 | 替代方案可用性与成本没做过对比 | 先造替代再宣布;或改为"终止而不迁移" |
三类原因里最常见的是第二类的变体:公告发出去了,但从没被确认过"收到"。这与真实情况之间的差别很大——一份发到公告栏的废弃通知,和一个挨个确认过的迁移排期,是两个不同的东西。判断标准很直接:能不能列出调用方清单、每个调用方能不能找到人、对方能不能说出自己的迁移时间。
有些废弃压根不该推进,属于第三类:替代能力还没建好,或者迁移成本远高于继续维护的成本。这时更诚实的选择是"终止而不迁移"——明确宣布不再演进、只修安全问题,把成本写进预算,而不是常年挂着一个"正在废弃"的状态。
2. 四个阶段的顺序与判据
| 阶段 | 进入条件 | 退出判据 | 可回退性 | 主要风险 |
|---|---|---|---|---|
| 宣布 | 替代路径可用、名单已建立 | 名单上的调用方都有回执 | 完全可回退 | 公告无人读,误以为已完成 |
| 观测 | 调用量能按调用方与版本拆分 | 连续窗口内趋势稳定下降 | 完全可回退 | 把平台的自然衰减误判为配合 |
| 催办 | 趋势进入平台期 | 名单清零,或剩余方有书面迁移计划 | 可回退 | 变成每月例会的常驻议题 |
| 强制 | 到期且剩余调用方已被告知 | 调用量归零,或各方明确接受失败 | 不可回退 | 影响真实业务,事故回溯到这次决策 |
| 阶段 | 观测对象 | 完成信号 | 没做好会怎样 |
|---|---|---|---|
| 宣布 | 名单与回执 | 每个调用方都有人认领 | 公告成了自我安慰 |
| 观测 | 按调用方与版本的调用趋势 | 斜率稳定为负,或进入平台期 | 把自然衰减当成配合 |
| 催办 | 剩余调用方的迁移计划 | 计划有日期、有负责人 | 每月例会重复同一句话 |
| 强制 | 限流与错误返回的投诉量 | 投诉有明确出口与解释 | 事故被算成架构组的锅 |
观测阶段的判据是趋势,不是绝对量。 每天两万次听起来很多,但如果三个月前是每天十万次,这是一个正在自然消失的接口;反过来,每天五百次但半年一条直线,才是必须催办的硬骨头。绝对量反映的是这个接口的历史影响力,而趋势反映的是当下还有多少人在依赖它。
// 按调用方拆分,而不是只看总量
select caller_id, api_version,
count(*) as calls, count(distinct day) as active_days
from api_access_log
where api_path = '/v1/ORDER_ENDPOINT' and ts >= :since
group by caller_id, api_version
order by calls desc;
// 总量会被头部调用方掩盖;按 caller 拆开才能催办到具体的人
// 趋势判据用连续窗口的斜率与活跃天数,不用单点绝对值
拆分维度里最有用的是活跃天数:一个每天定时跑批的调用方,调用量不高但每天都出现,说明是有人在维护的常态化依赖;而一个只在某天刷过几千次的调用方,多半是历史脚本的一次性残留,可以直接联系删除。
// 从链路与网关侧交叉确认名单,避免只信一套数据源
$ grep -h "v1/ORDER_ENDPOINT" access-*.log \
| awk -F'\t' '{print $3}' | sort | uniq -c | sort -rn > callers.txt
$ join -v1 callers.txt contact-list.txt // 有调用、联系人表里没有 → 名单缺口
// 名单缺口本身就是执行卡点:找不到人,催办就无从下手
3. "降到零才删"与"到期就删"
这不是理念之争,而是取决于调用方是否可枚举、以及你对他们有多大的强制力。
| 情形 | 选择 | 代价 | 前置条件 |
|---|---|---|---|
| 调用方可枚举且都响应 | 降到零再删 | 周期长,可能需要数个窗口 | 名单可闭环 |
| 调用方不可枚举(公网、爬虫、未知客户端) | 到期就删,配合明确错误 | 短期报错与投诉 | 公告与替代路径已到位 |
| 内部强管控(网关、注册中心可观测) | 到期强制,按调用方分批 | 影响可控 | 变更流程支持 |
| 调用方无升级能力(老旧终端、嵌入式) | 长期保留 | 永久维护成本 | 必须显式记账并进预算 |
// 强制手段阶梯:从可逆到不可逆,逐级加压
// ─── 1 限流:把配额降到真实用量的某个比例,观察投诉与降级行为
// ─── 2 配额递减:按周下调,让调用方在监控上看见趋势
// ─── 3 明确错误:返回可识别的弃用错误码 + 迁移指引
// ─── 4 灰度下线:按调用方分批关闭,白名单逐批清空
// 每一级都要能独立回退,且回退动作在一个命令内完成
// 最差的下线方式是静默返回空数据:调用方只能靠猜
{"error":{"code":"ENDPOINT_DEPRECATED",
"sunset":"RFC3339 日期",
"replacement":"/v2/ORDER_ENDPOINT",
"owner":"服务台账中的负责人",
"doc":"迁移指引地址"}}
阶梯里最容易被跳过的是第一级。限流既是一次真实的影响探测(有多少调用方会在被限流时告警),也是一次通知(对方监控会亮起来,比邮件有效),成本又完全可逆。
4. 两类废弃、兼容记账与验收判据
| 类型 | 含义 | 难度来源 | 执行方式 |
|---|---|---|---|
| 能力型 | 功能被新能力替代,语义相近 | 迁移面广,但路径清晰 | 按调用方分批迁移,独立推进 |
| 结构型 | 系统被重写,边界与数据模型都变 | 语义映射与数据迁移困难 | 随重写阶段一并下线,不单独走流程 |
结构型废弃单独推进的典型后果是:重写还没完成,两套语义之间的兼容层先长成了必须长期维护的第三套系统。能随重写一起下线的部分,不要抽出来做成独立的废弃项目。
兼容代码的成本要显式记账,否则"再留一个季度"永远是一句没有数字的决定:
// 兼容层记账:每多留一天,账上就要有对应条目
// ─── 多一条分支:每次改动都要在两条路径上验证
// ─── 多一组用例:兼容用例持续占用分层预算
// ─── 多一份文档:新人要在两套语义之间来回切换
// ─── 多一个口径:指标与告警要按版本拆分
// 记账的意义是让"再留一个季度"变成一个有价格的选择
有一个信号比调用量更能说明问题:废弃了的东西还有人改。出现新的提交意味着仍有真实需求绑在上面,此时正确动作不是加大强制力度,而是把决策回退到观测阶段,优先补齐替代路径的缺口。强行推进只会把一个技术问题变成跨团队冲突。
验收要四件齐备,缺一件就等于没完成:代码删除(含配置与开关)、数据迁移完成(旧的存储与索引可下线)、文档与监控同步清理(否则新人照旧使用、告警继续指向已删接口)、调用方名单闭环(每个调用方有明确结论:迁走、终止或写明保留理由)。
| 指标 | 定义 | 反向信号 |
|---|---|---|
| 废弃周期 | 从宣布到代码删除的时长 | 只在"宣布"阶段有记录 |
| 下降斜率 | 单位时间的调用量下降比例 | 长期为零却被报成"进度良好" |
| 遗留条目数 | 台账中未关闭的废弃项 | 只增不减 |
| 复发次数 | 被重新启用或延期的次数 | 延期无理由、无新到期日 |
5. 排错:六种执行卡点
| 现象 | 根因 | 处理 |
|---|---|---|
| 公告发了没人理 | 没有回执机制与名单 | 先出名单,逐方要回执与迁移时间 |
| 调用量降不动 | 缺替代路径,或迁移成本高于收益 | 补替代;否则改为终止而不迁移 |
| 删了又被启用 | 有未迁移的数据或未闭环的调用方 | 删除前完成数据迁移与名单闭环 |
| 投诉集中在同一批 | 该批调用方无升级能力 | 从强制降级为限流 + 长期保留并记账 |
| 监控告警仍指向旧接口 | 验收只做了代码删除 | 文档、告警、仪表盘同步清理 |
| 新人继续使用旧接口 | 文档与示例代码未更新 | 迁移与文档作为同一交付物验收 |
6. 边界:这些废弃不必走完整流程
| 场景 | 做法 | 理由 |
|---|---|---|
| 只有一个内部调用方,可同步升级 | 直接改调用方后删除 | 不需要观测与强制阶段 |
| 成本极低的老接口(纯读、无状态) | 保留,登记为长期技术债 | 下线成本高于共存成本 |
| 随系统整体下线的接口 | 并入整体下线计划 | 单独推进只会拉长双份维护期 |
| 仍在对外承诺期内的 API | 按承诺期排期 | 排期依据是承诺,不是工程偏好 |
| 暂无任何调用方 | 立即删除,不留兼容层 | 观测阶段没有信息量 |
一句话收束:废弃的完成标准不是"我们宣布过",而是"名单闭环、代码消失、数据和文档都换了地方,并且它没有回来"。
自测
- "宣布废弃"和"执行废弃"之间差的是哪些具体动作?
- 三类执行不下去的原因里,哪一类意味着这个废弃本来就不该推进?
- 观测阶段为什么看趋势而不是绝对量,用什么信号区分平台期与自然衰减?
- "降到零才删"与"到期就删"的选择依据是什么?
- 结构型废弃为什么不宜单独走一套废弃流程?
- "废弃了还有人在改"这个信号应该触发什么动作?