phase 5 part 5: cs algorithms, network-os, web-security topics with tested examples; all 43 stacks pass --strict
This commit is contained in:
1 parent
f695bbd7cc
commit
691f738f5f
22 files changed
+1955
-37
No files matched your search
@@ -0,0 +1,84 @@
|
||||
---
|
||||
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。”
|
||||
Reference in new issue
Block a user