140 lines
7.0 KiB
Markdown
140 lines
7.0 KiB
Markdown
---
|
||
title: "当两个真相打架"
|
||
description: "删掉的测试数据自己回来了、机房视角全绿客户端全红、界面说 236 个接口文档说 91 个——今天所有问题都是同一个形状:系统里有两个都说自己对的东西,打起来了"
|
||
slug: when-truths-collide
|
||
date: 2026-10-15T18:12:31+08:00
|
||
lastmod: 2026-10-15T19:10:00
|
||
tags:
|
||
- 真相源
|
||
- 自动化
|
||
- 视角
|
||
- 教训
|
||
categories:
|
||
- 运维手记
|
||
---
|
||
|
||
今天处理完所有问题回头一看,发现它们是同一个形状:
|
||
|
||
**系统里有两个都说自己对的东西,打起来了。**
|
||
|
||
而裁判席上,经常没人。
|
||
|
||
## 打架一:删了又回来
|
||
|
||
同事报了个怪现象: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。*
|
||
|
||
*最值钱的一句:**两个自动化打架,不是因为谁错了,而是因为它们互相不知道对方的存在。***
|