系列第 7 篇。上一篇《三道闸门》讲的是"上不上的决策",这篇讲一个更隐蔽的坑------它不发生在上线前,而发生在你以为一切已经修好之后。
一、事故还原:一条"已执行"的指令
先说结论,再讲故事:我有一条修复指令,台账里按"已执行"记了三天。第四天独立复核时去读现网源码------它从未被执行过。而且就算执行了也不会生效,因为指令本身写错了两处。
背景:一个支付抵扣模块,其中"充值"功能本期不上线,但代码里的两个定时任务依然在跑------数据库表没建,任务每 10 分钟刷一次异常栈,一天下来约 5000 行日志噪音。真实的告警线索全被淹在这堆噪音里。
处理方式看起来很标准:给定时任务加配置开关,关掉它。指令下发了,台账记了"已执行"。
三天后做例行复核,我登服务器读现网源码,发现了三层问题:
键名写错、落点写错,这类错误不会报任何错------配置读不到就静默取默认值,"修了等于没修",且系统看起来一切正常。
二、病根:我把"指令下发了"当成了"事情做完了"
复盘这次的根因,不在编码层,在流程层:
之前的系列文章里写过"本地全绿、生产失效"------那是代码层的坑(mock 和你的错是匹配的)。这次的坑高一个层级:
"交付了" ≠ "生效了"。 复核的对象应该是"现网正在运行的东西",而不是"交付方的自述"。
具体到我的流程,有三个坏习惯叠加:
- 台账记了"已执行",但没有记"谁验证的、证据是什么" ------"已执行"是转述,不是证据
- 复核默认信了指令回执------指令下发成功 ≠ 代码修改成功,更 ≠ 配置生效
- 改配置前没有回查"真身" ------键名的全路径、落在哪个文件、哪个 profile 激活,全凭记忆写
三、修复与固化:三条可抄的纪律
纪律 1 · 复核三到现场
凡是"修 bug / 改配置"类交付,复核时必须亲自到三个现场取证:
自述不作为证据。交付方说"已执行",你的动作是去现场看,不是点头。
纪律 2 · 配置"键名 + 落点"必须回查真身
写任何配置变更前,两步取证:
- 键名全路径 :在源码里 grep 前缀(
@ConditionalOnProperty(prefix="aksu.recharge", name="task.enabled")→ 完整键名是aksu.recharge.task.enabled,少一层前缀就是静默失效)
- 落点文件 :grep 现网所有
application*.yml,确认改的是生产实际激活的那个 profile------别忘了 dev profile 可能在生产也是活的,你改了一个"看起来对"的文件,生效的是另一个
纪律 3 · 未上线模块的定时任务必须可关
规则:凡是本期不上线的功能,它的定时任务必须挂开关,且生产默认 false。否则"表没建/依赖没配"的异常会周期性刷屏------噪音不只是难看,它会把真正的告警淹掉。
四、连带的三个认知修正
这次复核顺带纠正了我三个习以为常的判据,一并分享:
| # | 旧认知 | 修正后 |
|---|---|---|
| 1 | 自动巡检报"正常"就是没问题 | 巡检只能取证,不能归因------它看不见配置/代码变更。曾有一次把"商户号配置错误"误归因为"通道抖动",因为巡检没有变更上下文。修正:巡检报告强制附一节"本期是否发生变更",有变更优先归因到变更 |
| 2 | systemctl is-active 是绿的 = 服务在 |
systemd 状态可能因为 unit 文件写法问题长期 failed,进程其实一直在跑 (也可能反过来)。判据改为:端口在听(ss -lntp)+ 实际请求通 |
| 3 | 服务重启计数基线一直沿用 | 重启计数的基线要随每次发布重置------旧基线会让"判断是否异常重启"的阈值彻底失真(我遇到过基线 131、实际应为 0 的情况,判据完全失真) |
五、写在最后
"本地全绿、生产失效"教会我:验证要在真实环境做 。 "交付了 ≠ 生效了"教会我:取证要在现网现场做,且永远不采信自述。
两层坑的本质是同一个:你相信了一个替代品------mock 是生产的替代品,自述是事实的替代品。流程设计的全部要义,就是不断把"替代品"从关键路径上拿掉,换成真东西。
这套复核纪律连同九阶段交付 SOP、三道闸门、十条工程纪律,全部开源:
GitHub:github.com/yang-shenxu/engineering-delivery-playbook
💬 评论钩子:你有没有过"以为修好了、其实没修"的经历?是多久之后发现的?评论区聊聊。
系列前作: 踩了 5 次"本地全绿、生产失效"之后,我把交付流程做成了 SOP 硬截止项目不赌命:我给交付流程装了三道"闸门"