aiohttp 请求生命周期
下面是你要的图 —— aiohttp 请求生命周期 + 各 timeout 对应阶段
我用流程图形式标出来每个超时参数作用的位置。
🧠 aiohttp 请求生命周期图
┌───────────────────────────────────────────────┐
│ total 超时范围 │
│ (整个请求从开始到结束的最大时间) │
└───────────────────────────────────────────────┘
│
▼
┌──────────────────┐
│ DNS 解析阶段 │
└──────────────────┘
│
│ ← connect 超时包含
▼
┌──────────────────┐
│ TCP 建立连接 │
└──────────────────┘
│
│ ← sock_connect 超时只管这一段
│
│ ← connect 超时也包含这一段
▼
┌──────────────────┐
│ SSL 握手 (HTTPS) │
└──────────────────┘
│
│ ← connect 超时包含
▼
┌──────────────────┐
│ 发送 HTTP 请求 │
└──────────────────┘
│
▼
┌──────────────────┐
│ 等待服务器响应 │
│ (TTFB) │
└──────────────────┘
│
▼
┌────────────────────────────────┐
│ 读取响应数据(可能是流式) │
│ │
│ 每次读取数据块之间的间隔 │
│ 受 sock_read 控制 │
└────────────────────────────────┘
│
▼
┌──────────────────┐
│ 请求完成 │
└──────────────────┘
🔎 每个 timeout 的作用总结
🟢 total
包住整个流程:
DNS → TCP → SSL → 发送 → 等待 → 读取
只要整体超过这个时间就超时。
🟢 connect
控制:
DNS + TCP + SSL
如果在“建连阶段”卡住,就触发。
🟢 sock_connect
只控制:
TCP 三次握手阶段
一般用于更精细控制。
🟢 sock_read(流式最重要)
控制:
两次数据块读取之间的最大间隔
⚠️ 不是总下载时间
⚠️ 不是整个响应时间
而是:
“服务器多久没发新数据就断”
对于 LLM streaming 非常关键。
🔥 再给你一个 LLM Streaming 场景例子
假设:
ClientTimeout(
total=None,
connect=10,
sock_read=30
)
发生情况:
| 场景 | 会不会超时 |
|---|---|
| 服务器 5 秒内连上 | ✅ 正常 |
| 建连 15 秒 | ❌ connect 超时 |
| 正常开始流式输出 | ✅ |
| 输出一半,40 秒没 token | ❌ sock_read 超时 |
| 一直有 token 但总共 5 分钟 | ✅ 不超时(因为 total=None) |
🎯 一句话理解图
connect = 建连阶段
sock_connect = 纯TCP阶段
sock_read = 数据读取阶段
total = 整个生命周期
如果你愿意,我还能再给你画一张:
👉 「连接池(TCPConnector)内部结构图」
👉 或 「流式读取时的时间轴示意图」
那两张图会让你彻底搞懂 aiohttp 在高并发 LLM 场景下是怎么运作的。
好,这张是你要的——
🧠 LLM 流式读取(Streaming)时间轴示意图
我们用“时间向右流动”的方式来看 sock_read 是怎么工作的。
✅ 正常流式情况(不会超时)
假设:
ClientTimeout(
total=None,
connect=10,
sock_read=30
)
时间轴:
时间 ───────────────────────────────────────────────▶
连接阶段
│
├── DNS/TCP/SSL (2s)
│
└── 建连完成
服务器开始返回流式数据(SSE)
Token1 Token2 Token3 Token4 Token5
↓ ↓ ↓ ↓ ↓
|----5s-----|----3s-----|----8s-----|----6s-----|
每两个 token 之间都 < 30 秒
✅ 不会触发超时
因为:
sock_read = “两次数据块之间最大允许间隔”
只要持续有数据,就不会断。
❌ 触发 sock_read 超时的情况
时间 ───────────────────────────────────────────────▶
Token1 Token2
↓ ↓
|-----4s------|
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
40 秒没有任何数据返回
↑
sock_read = 30 秒
结果:
asyncio.TimeoutError
原因:
超过 30 秒没有收到新的数据块
注意:
- 即使之前已经收到很多 token
- 只要“间隔太久”
- 就会断开
🔥 对比:如果设置 total=60
total = 60
sock_read = 30
情况 1:持续有 token,但总耗时 70 秒
Token持续不断
但总时间到 60 秒
❌ 会触发 total 超时
情况 2:total=None
Token 持续 5 分钟
只要每 30 秒内有新 token
✅ 永远不会因为时间总长而断
这才是大模型 streaming 推荐设置。
🎯 更直观理解:计时器是怎么“重置”的?
sock_read 的计时方式:
收到一个 chunk
↓
启动 30 秒倒计时
↓
如果 30 秒内收到新 chunk
↓
重置计时器
↓
继续
所以它不是“累计时间”,而是“空闲时间”。
🧩 再给你一个“真实 LLM 卡死场景”
有些模型:
- 生成到一半
- 上游卡住
- 连接没断
- 但不再发送 token
如果你没有设置 sock_read:
程序会一直挂住
如果你设置:
sock_read=30
30 秒后自动断开
这就是生产环境必须控制的点。
🏗 总结这张图核心
sock_read = 控制“空闲时间”
total = 控制“总时间”
connect = 控制“建连时间”
如果你愿意,我可以再画一张:
👉 「流式 + 重试 + 指数退避」完整时间轴图
👉 或 「高并发下连接池复用时间线图」
那两张会更偏生产级架构理解。
好,这张是你要的——
🏗 高并发下连接池复用时间线图(aiohttp)
我们来看:
- 多个协程并发请求
- TCPConnector 如何分配连接
- keep-alive 如何复用
- limit / limit_per_host 如何生效
🧠 场景设定
TCPConnector(
limit=4,
limit_per_host=2,
keepalive_timeout=10
)
并发 6 个请求:
Task A
Task B
Task C
Task D
Task E
Task F
目标:
https://api.llm.com
🕒 时间线图
时间向右 →
时间 ─────────────────────────────────────────────▶
第 1 阶段:前两个请求建立连接
Task A ──TCP1───────使用中──────────────┐
Task B ──TCP2───────使用中──────────────┘
连接池状态:
[ TCP1*, TCP2* ]
* 表示使用中
⚠️ 因为:
limit_per_host = 2
所以同一个 host 只能 2 个连接。
第 2 阶段:Task C / D / E / F 到来
Task C 等待连接
Task D 等待连接
Task E 等待连接
Task F 等待连接
此时:
连接池已满(2/2)
这些任务进入:
连接池等待队列
第 3 阶段:Task A 完成
Task A 完成
TCP1 进入空闲状态(keep-alive)
连接池:
[ TCP1 (空闲), TCP2* ]
此时:
Task C 立刻复用 TCP1
时间线:
Task C ──复用TCP1───────────────
注意:
没有重新建 TCP
没有 DNS
没有 SSL
👉 直接发 HTTP 请求
第 4 阶段:Task B 完成
TCP2 变为空闲
Task D 复用 TCP2
连接池:
[ TCP1*, TCP2* ]
🔥 关键理解:连接复用发生在这里
建立一次 TCP
↓
多个请求复用
↓
直到 keepalive_timeout 到期
⏳ keepalive_timeout 生效图
假设 10 秒内没有新请求:
TCP1 空闲 10 秒
TCP2 空闲 10 秒
时间线:
空闲 ─────────10秒─────────▶ 关闭连接
连接池变成:
[ ]
下次请求会重新建 TCP。
🚦 limit 和 limit_per_host 同时作用
假设我们再加一个 host:
api.llm.com
api.another.com
如果:
limit=4
limit_per_host=2
最大情况:
api.llm.com 2 连接
api.another.com 2 连接
超过 4 个总连接就会排队。
🧠 高并发真实效果总结
在 LLM 场景中:
1000 并发请求
但如果你设置:
limit_per_host=50
实际同时存在的 TCP 连接只有 50 个。
其他请求:
- 要么排队
- 要么复用已完成的连接
📊 把整个过程浓缩成一张逻辑图
请求进入
↓
是否有空闲连接?
↓ ↓
有 没有
↓ ↓
复用TCP 是否达到limit?
↓ ↓
否 是
↓ ↓
建立新TCP 排队等待
🎯 生产级理解重点
1️⃣ limit 控制资源上限
2️⃣ limit_per_host 防止打爆单个上游
3️⃣ keepalive 减少 TCP/SSL 开销
4️⃣ 高并发时大多数请求在“排队 + 复用”
如果你愿意,我可以再画一张更底层的:
👉 「单个 TCP 连接内 HTTP 请求复用 vs 多 TCP 并发对比图」
👉 或 「连接池 + 重试 + 限流 组合时间线图(真实生产模型调用场景)」
那会更贴近真实大模型网关架构。

浙公网安备 33010602011771号