测试全过、上线就崩?3 个环境差异隐形杀手:时区差 8 小时只是头一个
你看着 CI 流水线一路飘绿,信心满满点下发布,结果 5 分钟内告警炸响。这种"测试全过、上线就崩"的噩梦,几乎每个跑过生产环境的人都经历过。先别急着骂代码,有 80% 的项目问题都集中在环境差异那 3 个场景里,而且它们全都不会在测试环境现身。
三个杀手分别是:时区与时间戳漂移、网络策略的"温柔一刀"、以及"一加日志就消失"的幽灵 Bug。它们不报错、不超时、不崩溃,只是安静地让结果变错——你以为代码没问题,其实环境早就替你做错了决定。
见过一个团队花两周搞"全场景全覆盖",结果连最基本的"订单查询"都还在漏单号,纯属本末倒置。先把 3 个核心场景的基线打牢,扩展才有意义。
杀手 1:时区与时间戳漂移——"今日优惠"Agent 以为今天是昨天
测试环境用 UTC,生产环境用东八区,这种差异平时毫无存在感——直到 Agent 的逻辑依赖"当前时间"。
一个"今日优惠"Agent 就栽在这上面:测试时用 datetime.now() 拿当前时间,一切正常;上线后生产服务器系统时区未同步,Agent 算出来的"今天"其实是昨天,优惠活动按昨天的时间戳执行,全部失效。从日志看,代码执行路径、参数、返回全都对,就是时间错了。
排查这类问题的思路只有一条:所有时间相关逻辑必须显式声明时区,禁止裸调用 now()。
两个落地动作:
① 启动时强制注入时区,在 Runner 里加上 --timezone Asia/Shanghai:
# 启动命令
eac-runner start --timezone Asia/Shanghai
② 所有 Agent 启动后的首行日志就打印时区,让"当前是什么时区"永远可查:
import datetime
from zoneinfo import ZoneInfo
TZ = ZoneInfo("Asia/Shanghai")
print(f"Current timezone: {TZ}") # 首行日志,排查时一眼定位
# 显式声明时区,禁止裸调用 datetime.now()
now = datetime.datetime.now(TZ)
配套的还有一条纪律:定时任务的时间字段全部写死时区后缀(比如 08:00+08:00),不依赖服务器默认设置。时区不是配置项,是业务逻辑的一部分。
杀手 2:网络策略的"温柔一刀"——不是 timeout,是静默丢包
这个杀手更阴:测试环境允许所有出站请求,生产环境有严格防火墙。Agent 调用外部 API 时,被防火墙静默丢包——注意,不是返回 timeout,是请求直接消失。
结果是:自动化测试里 API 调用全部成功(因为 Mock 了网络层),生产环境里 RAG 检索永远返回空结果,没有任何报错。你检查 API Key、检查超时配置、检查模型参数,全都查不出问题——因为问题根本不在这些地方。
排查技巧:在沙箱环境部署一个"网络探针"服务,Agent 每次调用外部 API 前先向探针发一次心跳。
#!/bin/bash
# scripts/net-probe.sh —— 调用外部 API 前先探活
curl -s -o /dev/null -w "%{http_code} %{time_total}s" \
--connect-timeout 3 "https://api.example.com/ping" || echo "PROBE_FAILED"
探针会记录每一次请求的 IP、端口、耗时。当发现某个 API 在探针日志里有记录、但在 Agent 日志里"消失"时,基本可以断定是网络拦截——有团队就靠这个办法发现了 3 次被防火墙策略误杀的 API。
判断标准就一句话:公开页面可以另开浏览器验证,依赖外部 API 的生产调用必须走探针验证。
杀手 3:幽灵 Bug——一加日志,问题就消失
最让人抓狂的 Bug 长这样:为了排查问题往代码里加了几行日志,重新部署后,问题突然不再出现;删掉日志,它又回来了。这不是灵异事件,是你加的日志操作本身引入了额外执行时间,打乱了原有的时序,导致问题不再触发。
一个真实案例:异步任务偶尔丢失处理结果,加了详细日志后问题消失。最后改用在线诊断工具(Arthas、JFR 这类无需重启服务的工具)直接查看线程堆栈和方法耗时,才定位到是任务调度顺序异常。
幽灵 Bug 有两层,排查方式完全不同:
| 类型 | 特征 | 排查方式 |
|---|---|---|
| 观测影响型 | 一加日志就消失,删掉又回来 | 用非侵入式诊断工具(Arthas/JFR),不改代码看运行时 |
| 状态积累型 | 跑几个月才出现,越跑越严重 | 查状态变化轨迹,不是盯单次请求 |
状态积累型的典型:一个消息推送系统运行 3 个月后开始偶尔漏推,排查发现是累计的消息偏移量未及时重置,部分消息被跳过——本地新起的服务状态全是干净的,永远复现不了。应对方式:结构化日志 + 全链路 TraceID,定期审查长期运行服务的状态趋势。
把"效果不好"翻译成"谁、何时、做什么":问题根因矩阵
环境差异问题排查完后,团队容易陷入"这次修好了,下次呢"的循环。解法是把每一次故障都沉淀进一张问题根因矩阵,它强迫你把模糊的"效果不好"转成具体的、可追责的条目:
| 失败场景 | 自动化指标 | 人工评估维度 | 根因类型 | 具体表现 | 解决方案 | Owner | ETA |
|---|---|---|---|---|---|---|---|
| 订单查询 | OCR 准确率 92% | 可操作性 2/5 | 知识库过期 | 返回的查询入口链接已失效 | 更新知识库+增加链接有效性校验 | Knowledge Manager | D1 |
| 请假指引 | fallback 触发率 15% | 安全性 0/5 | Prompt 缺陷 | 对"病假"未要求提供证明材料 | 重写 prompt 增加约束条款 | Prompt Engineer | D2 |
| 方言提问 | 语义理解准确率 68% | 同理心 1/5 | 训练数据缺失 | 未收录"俺""咱"等北方方言代词 | 补充方言训练集微调 embedding 模型 | ML Engineer | D5 |
这张表是团队复盘会的头一份材料,也是新人接手时最快理解系统瓶颈的钥匙——它把"系统好像不太行"变成"谁、何时、做什么、多久能好"。
上线前 6 项环境体检清单
把 3 个杀手和矩阵表打包成一份上线前检查清单,照着逐条过:
- 时间相关逻辑显式声明时区(zoneinfo / Asia/Shanghai),禁止裸调用
now() - Agent 启动后首行日志打印当前时区
- 沙箱部署网络探针,外部 API 调用前先心跳探活
- 上线前核对环境差异清单(时区/防火墙/权限/依赖版本)
- 用容器统一 OS 与依赖版本,脱敏后拉取线上真实数据做回归
- 生产故障统一进根因矩阵,附 Owner 与 ETA
关键句:环境差异不会报错,它只会让结果悄悄变错——所以"测试全过"从来不是上线的充分条件。
写在后面
这三个杀手有个共同点:它们都在"代码之外",靠单元测试和 CI 永远发现不了。想让 Agent 和自动化任务在生产环境稳跑,把环境一致性当成一等公民来管——时区、网络策略、运行时观测,每一项都比再多跑一轮测试有价值。
你的线上任务有没有遇到过"测试全过、上线就崩"的诡异时刻?最后是怎么定位的?欢迎评论区聊聊,特别是那种"查了两天才发现是环境问题"的案例。
(下一篇预告:多平台分发里的 3 个"假成功"——工具说成功、页面却是错的,4 项断言把它拦在发布前。)