一次 generate-prompts 服务连续超时事故的完整排查记录
背景
一个平时很稳定的服务,在 2026-04-02 这天突然出现“连续失败”。
最让人难受的不是失败本身,而是失败信息太少:日志里只有一串「第 1 次请求失败」,没有异常类型、没有耗时、没有栈。
这种时候人的直觉会把怀疑撒向四面八方:逻辑是不是坏了、参数是不是不对、上游是不是抽风、网络是不是波动……但没有证据,一切都只是猜。
1. 先把故障“照亮”:只补日志,不动行为
线上系统已经跑了很久,第一原则是:先让问题可见,但不要一上来就改主逻辑。
我加的日志只做两件事:
- 把“这次请求到底发生了什么”讲清楚
- 保持所有行为不变(重试次数、超时、请求参数、返回解析都不动)
具体补充项包括:
- 请求开始时的关键信息(目标地址、超时、参数摘要、prompt 长度)
- 当前是第几次重试、总重试次数
- 每次请求耗时
- 异常类型与
repr(error) - traceback
-(如果有)上游返回的原始错误体
这一步的价值在于:它不会“修复”任何东西,但会把原本模糊的失败变成可定位的问题。
2. 新日志把矛盾点钉死在「连接阶段超时」
日志增强后,很快就捕捉到关键一行:
第 1/3 次请求失败, elapsed=180.86s, type=TimeoutError, repr=TimeoutError()
再看栈,核心落在:
TimeoutError: TimeoutError()
Traceback (most recent call last):
File "/app/services/llm_service.py", line 107, in generate
response = await asyncio.wait_for(...)
File "httpcore/_async/connection.py", line xxx, in _connect
...
File "httpcore/_backends/anyio.py", line xxx, in connect_tcp
...
TimeoutError
核心落点是:
connect_tcp_connect
这意味着什么?
超时发生在 TCP 连接建立阶段。
也就是说,请求连“HTTP 响应处理”都没进入,更谈不上“业务返回了错误”。
到这里,很多直觉猜测可以先放下:
- 不是返回体解析的问题
- 不是返回格式不对
- 不是应用层报错没处理
更像是:这台机器根本连不上对方的 443 端口。
3. 把战场从代码切到网络:在机器上做连通性验证
既然栈指向了连接阶段,下一步就不该继续盯代码,而应该直接在服务器上做最朴素的网络验证。
3.1 DNS:先确认域名能解析
getent hosts upstream.example.com
能解析出 IP,说明 DNS 本身不是问题。
3.2 curl:直连 443,看是否能建立连接
curl -v --connect-timeout 5 --max-time 10 <https://upstream.example.com>
输出停在类似:
Trying xxx.xxx.xxx.xxx...
Connection timed out after 5001 milliseconds
这个信息非常“硬”:
- 已经解析到 IP
- 已经开始尝试连接
- 但 卡死在 TCP 建连
- 连 TLS 握手都没开始
3.3 更直接:探测端口是否可达
timeout 5 bash -c '</dev/tcp/110.42.10.198/443' && echo OK || echo FAIL
返回 FAIL。
到这里基本闭环:不是某个 API 路径出错,而是 443 端口就是连不上。
4. 结论:表象是超时,根因是 TCP 连接没建立
这次故障表面看是“接口一直超时”,但真正的根因更底层:
服务器无法与 upstream.example.com:443 建立 TCP 连接。
换句话说:请求还没进入应用层,已经在连接层死掉了。
5. 这次排查里最有用的经验
5.1 不要让“失败但无细节”的日志拖着你猜
如果日志只写“失败了”,排查就会变成玄学。
对线上系统来说,日志不是装饰品,而是你在事故里唯一可信的传感器。
5.2 先确认请求到底卡在哪一层
当你看到连续超时,优先问:
- 是 DNS?
- 是 TCP connect?
- 是 TLS handshake?
- 是 HTTP 响应慢?
- 还是应用层返回错误?
把“卡在哪一层”确定下来,复杂问题往往会瞬间变简单。
6. 这次用到的关键命令(备忘)
getent hosts upstream.example.com
curl -v --connect-timeout 5 --max-time 10 <https://upstream.example.com>
curl -v --connect-timeout 5 --max-time 10 <https://upstream.example.com/v1/models>
openssl s_client -connect 110.42.10.198:443 -servername upstream.example.com -brief
timeout 5 bash -c '</dev/tcp/110.42.10.198/443' && echo OK || echo FAIL
总结
这次最重要的不是“修了哪个 bug”,而是确认了一件更基础的事:
当外部调用连续失败时,除了看代码,也一定要尽快确认机器是否真的连得上对方的端口。
很多事故并不是业务逻辑坏了,而是请求根本没走到业务逻辑那一层。
陕公网安备61011302002223号