Files
zhuangang/topics/cs/network-os/scenarios/connection-troubleshooting.md
T

85 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: cs.network-os.scenario.connection-troubleshooting
title: 线上连接问题排查:大量 TIME_WAIT、连接池耗尽、fd 用尽和事件循环阻塞
type: scenario
domain: cs
stack: [network-os, linux, fastapi]
tags: [TIME_WAIT, 连接池, 文件描述符, 事件循环阻塞, 超时, 排查]
level: intermediate
prerequisites: [cs.network-os.http-tcp, cs.network-os.os-basics]
est_hours: 2
related: [cs.network-os.interview, devops.linux.scenario.incident-troubleshooting]
week: [14, 16]
---
# 线上连接问题排查:大量 TIME_WAIT、连接池耗尽、fd 用尽和事件循环阻塞
## 业务背景与问题
AI Agent 服务(FastAPI)高峰期偶发大量超时,日志中出现:
- 调用模型网关报 “Cannot assign requested address”;
- 偶尔出现 “Too many open files”;
- 所有请求的延迟同时变高,包括健康检查。
## 约束
- 不能停机排查。
- 运维只给了容器内的只读排查权限。
## 方案设计
1. **看连接状态**:`ss -s`、`ss -tan state time-wait | wc -l` 发现几万个 TIME_WAIT,目标都是模型网关 → 代码中每次请求都新建 `httpx.AsyncClient`,用完就关闭,没有复用连接。**修复**:应用级共享一个客户端(连接池),设置池大小和 keep-alive。
2. **看 fd**:`ls /proc/<pid>/fd | wc -l` 与 `ulimit -n` 对比 → 文件上传后临时文件没有关闭。**修复**:使用 `with` 管理资源;在监控中加入 fd 数量。
3. **看事件循环**:所有请求同时变慢,说明事件循环被阻塞 → 用 asyncio 调试模式 / 采样分析发现同步的 PDF 解析。**修复**:移到进程池(`run_in_executor`)或任务队列。
4. **超时与隔离**:所有外部调用设置连接超时和读超时;对模型网关设置并发上限(信号量),防止一个慢依赖耗尽所有资源。
5. **验证**:压测对比修复前后的 TIME_WAIT 数量、fd 数量、P99 延迟。
## 关键代码
```python
from contextlib import asynccontextmanager
import httpx
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# 整个应用共享一个客户端:复用 TCP / TLS 连接
app.state.llm = httpx.AsyncClient(
base_url=LLM_GATEWAY,
timeout=httpx.Timeout(connect=3, read=60, write=10, pool=5),
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
)
yield
await app.state.llm.aclose()
app = FastAPI(lifespan=lifespan)
@app.post("/parse")
async def parse(file: UploadFile):
data = await file.read()
loop = asyncio.get_running_loop()
return await loop.run_in_executor(process_pool, parse_pdf, data) # CPU 密集任务不阻塞事件循环
```
相关原理:[网络基础](../01-http-tcp.md)、[操作系统基础](../02-os-basics.md);Linux 排查命令见 [Linux 线上排查](../../../devops/linux/scenarios/incident-troubleshooting.md)。
## 取舍
| 选择 | 优点 | 缺点 |
| --- | --- | --- |
| **复用连接(本方案)** | 根治 TIME_WAIT,同时降低延迟(省去握手) | 要管理客户端生命周期 |
| 调整内核参数(端口范围、TIME_WAIT 复用) | 立刻见效 | 治标不治本,有副作用,容器内往往无权限 |
| 增加实例 | 简单 | 掩盖问题,成本上升 |
## 易错点
- 看到 TIME_WAIT 就认为是服务器的问题——它出现在主动关闭的一方。
- 共享的客户端没有设置池上限,慢依赖拖垮整个服务。
- 只设置了总超时,没有区分连接超时和读超时(流式响应的读超时要考虑 token 间隔)。
## 面试怎么讲
> “Agent 服务高峰期出现‘Cannot assign requested address’,用 ss 看到几万个 TIME_WAIT 指向模型网关,原因是每次请求新建 HTTP 客户端。改成应用级共享客户端并配置连接池和分阶段超时后,TIME_WAIT 基本消失,延迟也下降了。另外发现同步 PDF 解析阻塞事件循环,导致所有请求同时变慢,移到进程池解决。修复后用压测对比了连接数、fd 数和 P99。”