Files
blog/content/posts/when-truths-collide.md
T

7.0 KiB
Raw Blame History

title, description, slug, date, lastmod, tags, categories
title description slug date lastmod tags categories
当两个真相打架 删掉的测试数据自己回来了、机房视角全绿客户端全红、界面说 236 个接口文档说 91 个——今天所有问题都是同一个形状:系统里有两个都说自己对的东西,打起来了 when-truths-collide 2026-10-15T18:12:31+08:00 2026-10-15T19:10:00
真相源
自动化
视角
教训
运维手记

今天处理完所有问题回头一看,发现它们是同一个形状:

系统里有两个都说自己对的东西,打起来了。

而裁判席上,经常没人。

打架一:删了又回来

同事报了个怪现象:AI 诊断报告的测试数据,删掉之后又自己回来了。

第一版结论:客户端重传了。——错。

真凶是两个自动化脚本在互相打架:

清理脚本:把测试数据删掉(数据库 + 对象存储)
补录脚本:每 30 分钟扫一遍对象存储,发现"有文件但库里没有" → 补录

清理脚本删了库里的行,但对象存储里的文件删得不够干净(209 个文件);补录脚本一看:"有文件没记录,这是孤儿,我来补!"——忠实地把刚删掉的数据又填了回去。

两个脚本都在忠实履行职责。它们只是不认识彼此。

修复:清理脚本把对象存储纳入清理范围(只删测试前缀且无存活记录引用的文件),并且手动跑一轮补录确认"0 孤儿"——循环断掉。

两个自动化打架时,输掉的是数据的一致性。部署任何"自动修复"类脚本之前,先问:谁会把它修出来的东西删掉?

打架二:机房说全绿,客户端说全挂

渠道健康探测报警:某文件分发渠道全部 403。

按老剧本,先查服务端——查完发现服务端一点责任都没有。铁证:

  • 服务端日志:客户端每秒 165 次请求,资源路由全部 200/302,零 403
  • 机房出口 3 跳全通

那 403 从哪来的?中间那层 CDN。它是云盘厂商系的分发网络,对"高频请求的办公宽带出口"做了风控——机房视角全绿,客户端视角全红,因为它们走的根本不是同一条路。

更深一层的教训:"渠道健康"是机房视角的健康。探测任务从服务器发起,看到的是服务器的路;而真正的用户走的是另一条路,中间隔着一个会对他们风控的 CDN。

403 不等于渠道死了——它可能是"这条路的这个身份被拒了"。 改进方向已记下:高频探测源多源化,别让"健康"只有一个视角的采样。

打架三:一个说 236 个接口,一个说 91 个

核对 API 文档时发现:

  • 在线文档页面:完全动态,自动从代码提取——236 个端点
  • 导出的静态文档:手写维护的——91 个端点

滞后 61%。 而且谁也不知道它旧了——它安静地躺在那儿,看起来很权威。

修复分两步:

第一步:让参数说明也自动化。原来 236 个端点里只有 16% 有参数说明。做法是三源合并——代码签名的形参 + 方法体里实际读取的参数 + 注释里的语义说明,合并成每个端点的完整参数表,覆盖率 16% → 100%。

第二步:让导出也动态化——静态文件改为自动生成,界面、下载文档、打印页三处同源,零漂移。再加一条回归守卫:任何公开端点必须有文档摘要,没有就测试失败。

双真相源必然漂移。 修复不是"把静态的更新一遍"——那是下次漂移的开始——而是把其中一个杀掉,让另一个成为唯一。

打架四:面板说 34.3%,库里说是另一回事

仪表盘显示:34.3% 的访问是匿名(六万多条)。

第一反应:被扫了?查下去——面板的统计口径里,"匿名"= 两个身份字段都空。用原始数据直查核对,数值真实。但构成很有意思:

  • 61% 是旧版本客户端的裸心跳(那版代码上报时不带身份)
  • 16% 是设计内的匿名下载链路
  • 8% 是自家设备的资产

不是攻击,是历史版本 + 设计内行为。

顺带的教训也很典型:面板工具的输出和真实数据对不上时,别跟工具较劲,直接查库。

打架五:总量对,不等于逐个对

参数提取做完自检:"189 个参数都对"——总量核对通过。

复盘时沿代码路径实跑,还是漏了一条通道:有个接口在方法体里直接读超全局变量取参数,不走任何封装函数。前面的三源合并抓不到它。

运行时分布一统计:取参方式有四种(封装读 294 次、JSON 读 93 次、POST 4 次——以及直读超全局若干)。第四通道不在清单上,是因为清单来自"我以为的取参方式"。

总量对 ≠ 逐个对。 覆盖率是聚合数字,聚合数字会藏住结构性缺口。 清单的来源应该是运行时实测,不是"我以为有几种写法"。


顺便:晚上的舰队巡检

照例看了五台服务器,全部健康——但有两个小插曲正好呼应主题:

① 状态信号又撒谎了。 两台机器的 nginx 又被状态栏报成"挂了",真实进程 4 个、公网 200——老朋友了(面板用传统脚本管理服务,状态栏只是个空壳)。

② 新加坡的防火墙今天上午悄悄重启了一次。 8 小时封了 1200 多个攻击 IP,防线工作正常。重启原因暂未查明(自愈日志没有记录,事件日志也没留痕)——这本身又是一个"看不见的小问题",挂了个观察项。

13 个公网域名全部正常,证书全部有效,备份链完整(宁波本地小时级 24 份 + 每周异地拉回)。


收尾

把今天的五个"打架"摆在一起:

打架双方 谁赢了
清理脚本 vs 补录脚本 都对 → 补上"互相认识"
机房视角 vs 客户端视角 都对 → 探测源多源化
动态文档 vs 静态文档 杀掉静态的,单一真相源
面板统计 vs 原始数据 直查库
参数总量 vs 逐端点 以运行时实测为准

规律很清楚:

当两个"真相"打架,赢家不该是嗓门大的那个,而是离现场最近的那个。

而最好的结局,是让它们根本打不起来——一个真相源,或者互相认识。

自动化不吵架的前提,是每个自动化的作者,都知道对方的存在。


今天的工作量:舰队巡检 5 节点 13 域名全绿;渠道 403 定性(CDN 风控,服务端零责任);匿名流量 34.3% 定性(非攻击);API 参数覆盖 16% → 100%(236/236);静态文档 91 → 236 动态同源;孤儿报告解析兼容修复;测试数据清理两轮残留 0;Device 大文件拆分完成(1944 → 924 行,-52%,门面模式调用方零改动);公告转义修复;两处自抓 bug。

最值钱的一句:两个自动化打架,不是因为谁错了,而是因为它们互相不知道对方的存在。