--- 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//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。”