工程与管理迁移调试

迁移后 Connection Refused:被遗漏的 localhost 依赖

前一篇写了 MySQL + Redis 的持续同步方案,实际迁移中遇到一个意料之外的故障:告警服务迁走后,另一个还在老集群运行的服务报 Connection refused

排查半小时,根因一句话:代码里有一个 HTTP client 直接调 localhost:8097,但我们只检查了 Nginx 和 yml 配置,没搜代码。


故障回放

迁移步骤中,告警管理服务(alert-manager)是第一批迁走的。之前它和其他服务运行在同一台机器上,提供 localhost:8097 的 HTTP 接口。

迁走当天,另一个服务(trade-executor)开始疯狂报错:

ERROR: Connection refused: localhost:8097

第一反应:Nginx 反代配置没改?查了,改了。

第二反应:application.yml 里的 URL 没改?查了,也改了。

第三反应:搜代码。发现 DcAlertClient.java 里直接 hardcode 了 http://localhost:8097——不走 Nginx,不走配置中心,就是个本地直连。


为什么之前没问题

因为告警服务之前就在同一台机器上,localhost:8097 天然可达。运行时拓扑是这样的:

同一台机器 (老集群):
  trade-executor ──localhost:8097──► alert-manager

迁走后:

新集群: alert-manager :8097
老集群: trade-executor ──localhost:8097──► ❌ Connection refused

中间跨了机房,localhost 不再指向同一个东西了。


修复

两步修复:

临时止损:把 localhost:8097 改为 localhost:18097,走之前建好的 SSH 反向隧道:

老集群 trade-executor ──localhost:18097──► 隧道 ──► 新集群 alert-manager:8097

长期方案:添加 application-conoha.yml 覆盖,等全部服务迁到新集群后,改回 localhost:8097 同机直连。


漏了什么

迁移检查清单里有三层依赖要查:

层级检查方式这次查了吗
Nginx 反代grep nginx 配置✅ 查了
yml 配置rg yml 里的 URL✅ 查了
代码 HTTP clientrg Java/Go/Python 代码中的 URL❌ 漏了

第三层是最容易被忽略的。硬编码的 localhost 调用在微服务同机部署时完全合理——因为就是本机调用。但一旦服务拆分到不同机器,这些隐藏依赖就会爆炸。


元结构映射:广告系统的索引依赖

这和广告系统的一个经典事故一模一样:

某广告系统把正排索引拆到了独立服务(之前是进程内加载),迁移结束后发现召回模块疯狂超时。排查发现召回模块里有一段历史代码,直接通过本地文件路径读正排索引——没有走正排 RPC 服务。当正排数据不再落地到本地磁盘后,那段代码读到的是 3 天前的旧文件。

教训相同:迁移服务前,不是检查”你记着的依赖”,而是搜出”所有依赖”。 你不知道的事情才是真正会炸的。


迁移依赖检查清单

基于这次教训,补充一条检查项:

# 搜代码中所有 localhost / 127.0.0.1 引用
rg -n 'localhost|127\.0\.0\.1' --type java --type py --type go

# 搜代码中所有 HTTP URL(不只是 yml)
rg -n 'https?://' --type java --type py --type go | grep -v '^.*\.yml'

两个命令的区别:第一个搜所有 localhost(包括非 HTTP 的,比如 DB 连接),第二个搜所有 HTTP URL(包括远程域名)。两个都要跑。


教训

迁移检查清单的正确顺序:Nginx → yml → 代码。漏掉哪一层,哪一层就会在凌晨三点把你叫醒。

一句话:服务迁走后 Connection Refused 不是网络问题,是依赖没搜干净——local 调用的另一头已经不是 local 了。