健康检查全绿,模型调用全挂:一次"端口活着"的假保险

上周四凌晨两点,日更流水线卡在翻译步骤,三语发布一行都没进去。值班脚本吐出来的日志只有一句话:router 连接超时。几乎所有健康检查都是绿的——端口在、进程在、/health 返回 200。

专属插画
健康检查全绿,模型调用全挂:一次"端口活着"的假保险

健康检查全绿,模型调用全挂:一次"端口活着"的假保险

上周四凌晨两点,日更流水线卡在翻译步骤,三语发布一行都没进去。值班脚本吐出来的日志只有一句话:router 连接超时。几乎所有健康检查都是绿的——端口在、进程在、`/health` 返回 200。

现场还原

那台机器跑的推理服务前一周刚换过模型版本,上游把模型名从旧版 renaming 成了新版。我们的判断程序没有跟上:

- 健康检查脚本只 `curl /health`,端口活着就返回 `healthy`

- 真正的调用路径是 `/v1/chat/completions` 加具体模型名,这条路径没人碰过

- 旧模型名其实已经下线,调用全部 404,错误信息里写着 `model_not_found`

还有一层侥幸:换版本的 changelog 其实当天早上就到了群里,有两个同事看到了,都觉得"路由层应该会自动适配",没人去翻我们自己的配置清单。事后看,这是典型的"接口契约变更,两端各改一半"——上游改了名字,我们还在按旧名字调用,中间的监控又恰好只盯着"进程在不在",三层全都漏。

更麻烦的是,发布脚本对翻译失败的兜底逻辑是"重试三次后写占位内容继续跑"。也就是说,如果没人盯告警,当天三语文章会带着占位文案直接发线上,而且每个环节的单测都是绿的。占位兜底本来是给网络抖动用的,结果被一个"模型名错了"这种确定性错误钻了空子。

复盘里定下来的三件事

**1. 健康检查改成功能自检。** 新脚本不再只查端口,而是走和生产完全相同的路径:同一个接口、同一个模型名(从配置读,不写死)、同一份最小 prompt、同一个密钥加载链。一分钟一次,一次完整请求算一次心跳。任何一步 4xx/5xx 直接红。换模型那天我们已经吃过亏,所以模型名统一放在配置里,检查脚本和业务脚本读同一份,谁也不许在自己代码里写死名字。

**2. 兜底重试只给确定性错误用。** 把"重试后继续"收窄成:只对 5xx 和超时做有限重试,4xx(参数错、模型不存在、鉴权失败)立即失败并打红,绝不写占位。原则很简单——网络抖动可以兜,配置错误不能兜,兜上去就是拿线上内容替一条错误配置买单。

**3. 成功率进日报,而不是出事才看。** 每天汇总各步骤调用成功率、P95 延迟、4xx 明细,低于阈值直接告警。这次是凌晨两点人工发现的,如果当天流量更大或者告警延迟再久一点,占位内容就会发出去。事后没有"发现得好"这个说法,只能说监控没兜住,不能把运气当设计。

落地比听起来快:功能自检脚本就一个文件,复用发布脚本自己的密钥加载链和配置读取函数,不重新发明一套,避免"检查链路"和"业务链路"各用各的密钥配置。日报任务挂在已有的 cron 里,输出一张三列小表:步骤、成功率、最慢一档延迟。阈值先拍得糙一点(成功率低于 95% 告警),跑两周再收紧,别一上来就精细化调参,那属于过度设计。

给同跑道的人

如果你也是多节点流水线、模型名散落在各机器配置里,建议花半小时做两件事:先把所有"模型名"抽到一处配置,检查脚本和业务共用;再把健康检查从 `/health` 升级成一个真实的最小请求。前者是一次性的,后者能防住一整类"端口活着、服务全残"的问题。上次 404 的报错原文还留在告警归档里,格式、模型名、参数一个不缺——摆着提醒自己:绿的不一定是好的。

留言区

欢迎分享你的想法!

发表留言

0/500

加载留言中…