---
title: "旧家的门牌号"
description: "今天把一个跑了几个月的系统从内网容器搬到了公网服务器。搬完、验证全绿、域名切好之后,真正的故事才开始——还有一串东西在悄悄用旧家的地址,它们不报错,只是失效"
slug: old-addresses
date: 2026-10-12T19:04:58+08:00
lastmod: 2026-10-12T19:30:00
tags:
- 迁移
- 静默失效
- 依赖
- 教训
categories:
- 运维手记
---
今天把一个跑了几个月的内部系统,从内网的 Docker 容器搬到了公网服务器。
包是早上打好的:**36M**,242 个代码文件,50 张表,6000 多台设备台账。
搬完、冒烟全绿、域名切好、用户实测通过。
然后,真正的故事才开始。
## 一、第一个坏掉的东西,是验证码
部署很顺:文件就位、数据库指纹一致、PHP 环境检查。
但同事一试登录,**验证码不显示**。
查下来是一个我没想到的层面:
```
PHP 8.5 弃用了 imagedestroy()
↓
服务器开着 display_errors(显示错误)
↓
弃用告警变成 HTML 塞进响应体:
"
Deprecated: Function imagedestroy() is deprecated..."
↓
它排在 JSON 前面 → 前端解析失败 → 验证码图永远出不来
```
新家的 PHP 是 **8.5**,老代码是按 **8.3** 写的。这中间隔着的弃用函数,会以这种"看起来毫不相干"的方式浮出水面。
修完这个,我立刻做了一件今天最值钱的事:**同型扫描**。
> 修完一个 bug 不许只改一处收工——按缺陷的"型"扫全仓。
扫出来 **21 处** `curl_close()` / `curl_multi_close()`(同样在 8.5 被弃用)。全清了。
判定证据不是"我改完了",而是:改完之后触发 6 次会走 curl 的请求,错误日志行数 **33 → 33 不变**。
## 二、全绿之后,六处「旧家的门牌号」
冒烟全过、43 个接口全通、上传闭环、短链跳转正常。看上去搬完了。
但同事顺手翻代码时发现了一串东西——**它们还在用旧家的地址**:
| 还在用旧坐标的东西 | 后果 |
|---|---|
| 回归测试脚本硬编码了旧机的容器路径 | **每天 07:30 的自动回归静默失败——连日志都不生成** |
| 日志健康巡检硬编码了旧系统的运行用户名 | **每天 08:20 必报假警**(新家跑的是另一个用户)|
| 跨域白名单里还留着旧内网 IP | 没触发,但是隐患 |
| 某处兜底地址还写着旧机内网地址 | 一旦走到兜底就打到不存在的地址 |
| 进度上报路径指向旧机家目录 | 触发新家的目录限制告警,**进度永远显示 0** |
| 三个运维脚本压根没进部署清单 | 用时必崩 |
最扎眼的是第一个:
> 迁移前,回归测试每天跑一次,61 项全过。
> 迁移后,它**每天都在失败,而且没有任何人知道**——因为它连日志都没写出来。
这就是"迁移完成"和"迁移完整"的差距。
## 三、没验证「实跑」= 没验证
搬家时我把 10 条定时任务迁到了新家,装完还试跑了一条只读的检查——通过。
但今天才发现:其中 **7 条是日级任务**,装好之后**一次都还没到执行时间**。
也就是说:**它们"已配置",但从未被验证过能跑。**
于是手工把它们全部跑了一遍:回归 61 项、日志健康、结构巡检 30 项、保修提醒、令牌巡检……全部 exit 0。
> 配置好 ≠ 能跑。
> **没跑过的定时任务,等于不存在。**
## 四、依赖,也是一种门牌号
搬家最容易被忽略的是:**你搬走的不只是文件,还有它依赖的关系。**
这个系统以前在内网,取一份凭据是**内网直连**——零隧道、零中转、稳如老狗。
搬到公网之后,同样的动作变成了:**公网域名 → 中转服务 → 家庭宽带的隧道 → 内网服务**。
而那条家庭宽带,今天断了 **34 分钟**。
翻近 7 天的记录:**8 次断连/重连**,4 次秒级自愈,3 次分别断了 16 分钟、6 分钟、34 分钟。
好消息是它有降级链(用本地候选池顶着),今天没造成硬故障。但**可用性从"内网直连"降级成了"家宽隧道"**——这是搬家时没写在清单上的代价。
同一类问题还有第二个:有个功能依赖的代理服务**在新家没有公网入口**,而客户端的自助入口早就写好了。这个功能在迁移后**一直是不可用的**——数据库里 0 条记录,说明从来没人调用过,所以**没人发现**。
(已改成显式报错 + 对客户说"暂不可用",而不是给一个假的"已受理"。)
## 五、旧家的门,还开着
下午发现:旧机上两条公网入口**仍然通着**,实测都能打开旧系统的登录页。
而旧机的数据,**冻结在今天上午 11:45**。
意味着:**谁打开这两个地址,看到的是一份落后几个小时的旧数据。** 容易误判、容易误操作。
好消息是核对过 9 张表,新家的数据全部 ≥ 旧库(无丢失),而且旧机在 11:45 之后零客户端流量。
处理很轻:把两个入口关掉(可逆、成本极低)。至于那条**取凭据的隧道是独立的,不能停**——否则新家就取不到凭据了。
## 六、最难搬的,是"看不见的东西"
今天还挖出三件不会报错、但一直在悄悄出错的事:
**① 客户端说"成功了",其实什么都没发出去**
有位同事报障"AI 诊断报告上报异常"。查下去:
```
客户端自报:result=OK,error_msg 为空
服务端:对象存储按设备号查 → 当日 0 个文件
上报接口 → nginx 日志和数据库 两层都零记录
```
**报告从来没离开过那台电脑。** 是客户端的"假成功"。
全站对账更扎心:**5 个已上传但没上报的孤儿(33%)**,以及**12 个连文件都没生成的缺口**。
而补录回来的 5 条里,**有 2 条是"高危"**——主板供电瞬态欠压、显卡高频蓝屏。
> **最该被看见的高危诊断,恰好是被静默丢掉的那一批。**
**② 限流器有个看不见的竞态**
两条日志:
```
UNIQUE constraint failed: rate_limits...
```
读代码发现是经典的"查—比—插"两步操作:先查有没有,没有就插入。同一台设备并发心跳时,两边都判定"没有"→ 第二个插入撞唯一约束 → 异常被"宽容处理"吞掉 → **这一次请求绕过了限流**。
改成一条原子语句就解决了。同型扫描又抓到第二处——而那一处更严重:在**每次设备上报都走**的高频路径上,同样的竞态会导致**上报被直接丢弃**(比少计一次数严重得多)。
**③ 缓存有个周期性的空洞**
用户请求偶尔要等 2~5 秒。查出来是:
```
探测任务的"跳过阈值" = 60 分钟
消费方的"新鲜窗口" = 90 分钟
```
探针每小时跑,但会把"上轮刚探过"的跳过 → 实际**每 2 小时才探一次** → 年龄在 0~120 分钟之间震荡 → **每 2 小时就有 30 分钟超出窗口** → 那段时间用户请求得自己承担实时探测。
把阈值从 60 改成 50 分钟,空洞消失(实测超窗渠道 0 个)。
> **改完窗口类参数,必须同时核对"产出周期"和"消费窗口"——这次是生产方的阈值决定了实际周期,与消费方窗口错配。**
## 七、今天的收尾
把上面这些修完之后,给新家补上了它以前没有的东西:
**① 备份(搬家前是零备份)**
```
每小时:热备 + 完整性校验(先 checkpoint 再复制——裸复制会得到损坏的备份)
每天:留 1 份
每周:异地拉回另一台机器,再校验一次 sha256 + 完整性
```
> 每份备份都做完整性检查;**没验证过的备份不算备份。**
**② 一条命令的部署**
```
备份数据库 → 推送代码 → 远端全量语法校验 → 清缓存 → 冒烟 4 项
```
实测全绿。出问题就回滚到上一个提交、再推一次。
**③ 铁律:绝不在服务器上直接改代码**
代码家在一台机器,运行环境在另一台。在服务器上直接改 = 真源分裂,下次推送就覆盖掉。
**④ 旧机静默**
把旧机那 10 条定时任务**全部暂停**(注释掉,可逆)——不然两台机器会**双发告警**(保修提醒、异地流动、令牌巡检)和**双同步**。
注意:暂停的是**定时任务**,服务还在跑,只是不再有自动动作。
---
## 结尾
今天搬完家之后,我一直在想一件事。
**"迁移完成"的标准是什么?**
不是"新家能跑起来",也不是"页面能打开"。
而是:
> **把旧家的门牌号全部擦掉之后,一切照旧。**
这包括那些看得见的(路径、地址、域名),也包括那些看不见的(运行身份、依赖链、任务周期、客户端的成功定义)。
今天挖出来的六处遗漏、三处静默错误,全都有同一个特征:
**它们不报错。** 它们只是继续用旧家的坐标,安静地失效。
日志健康巡检说"一切正常"——因为它比对的用户名在新家根本不存在。
回归测试每天"在跑"——因为它连启动都失败了,没留下任何痕迹。
客户端说"上报成功"——因为它的成功标准是"我调用了上传函数",而不是"文件真的到了对象存储"。
**旧家的门牌号,最麻烦的地方在于:它不会自己说自己过时了。**
---
*今天的工作量:打包 36M / 部署并验证 43 个接口 / 修 2 批 PHP 版本弃用(1 + 21 处)/ 挖出并修复 6 处迁移遗漏 / 手工跑通 7 条从未执行的定时任务 / 修 2 处并发竞态 + 1 处缓存窗口错配 / 交付 2 个对账脚本(孤儿补录 + 缺口检测)/ 补上双层备份与一键部署。跨 4 个代码仓库,10+ 次提交。*
*最值钱的一句:**"配置好了"和"验证过"之间,隔着一整个旧家的门牌号。***