连接池复用¶
预先把连接建好放着,谁要用谁借,用完别关——高频场景下最简单有效的优化
核心概念¶
- 连接池 — 一批预先建好的连接集合,用时借出,用完归还而非关闭
- HTTP/1.1 Keep-Alive 复用 — 同一 TCP 连接上串行处理多个请求
- HTTP/2 多路复用 — 一个 TCP 连接上并行处理多个请求/响应
- 池化参数 — 最大连接数、最小空闲数、超时时间等关键配置
详解¶
没有连接池的世界¶
每次请求都要:建 TCP 连接 → TLS 握手 → 发数据 → 四次挥手关掉。
sequenceDiagram
participant C as 客户端
participant S as 服务器
loop 每次请求
C->>S: TCP 三次握手
C->>S: TLS 握手(HTTPS)
C->>S: GET /api/data
S->>C: 200 OK + 数据
C->>S: TCP 四次挥手
end
就像每次出门买菜都要:解锁 → 开门 → 走出去 → 锁门,回来再解锁 → 开门 → 走进去 → 锁门。一天买十次菜,光开关门就累死了。
有连接池的世界¶
sequenceDiagram
participant P as 连接池
participant S as 服务器
Note over P: 启动时建好 50 个连接
participant C as 请求
C->>P: 借连接 A
C->>S: GET /api/data(复用连接 A)
S->>C: 200 OK
C->>P: 还连接 A
C->>P: 借连接 B
C->>S: GET /api/user(复用连接 B)
S->>C: 200 OK
C->>P: 还连接 B
两个层次的复用¶
HTTP/1.1 连接复用(Keep-Alive)¶
sequenceDiagram
participant C as 客户端
participant S as 服务器
Note over C,S: TCP 连接(建一次)
C->>S: 请求1
S->>C: 响应1
C->>S: 请求2(同一个连接)
S->>C: 响应2
C->>S: 请求3(同一个连接)
S->>C: 响应3
但同一时刻只能有一个请求在跑(队头阻塞)。浏览器默认开 6 个连接弥补:
连接1: 请求1 → 响应1 (串行)
连接2: 请求2 → 响应2 (串行)
连接3: 请求3 → 响应3 (串行)
连接4: 请求4 → 响应4 (串行)
连接5: 请求5 → 响应5 (串行)
连接6: 请求6 → 响应6 (串行)
↑
6 个连接之间是并行的
HTTP/2 多路复用¶
一个 TCP 连接上同时跑多个请求/响应,通过帧编号区分:
TCP 连接(就一个)
├── 请求1 的第1帧 ──┐
├── 请求2 的第1帧 ──┼── 混在一起发,到了对端自动分拣
├── 请求1 的第2帧 ──┤
├── 请求3 的第1帧 ──┤
├── 请求2 的第2帧 ──┘
不再需要多个 TCP 连接来并行,一根连接全部搞定。
关键配置参数¶
| 参数 | 说明 | 过大 | 过小 |
|---|---|---|---|
| 最大连接数 | 池子最多同时借出多少个 | 内存和 fd 浪费 | 请求排队等连接 |
| 最小空闲 | 池子里至少保留多少个待命 | 空闲资源浪费 | 高并发时来不及建连 |
| 连接超时 | 借不到连接最多等多久 | 请求长时间挂起 | 高峰期大量失败 |
| 空闲回收 | 超过多久没用就释放 | 占着不用浪费资源 | 频繁重建连接 |
代码示例¶
Python requests¶
from requests.adapters import HTTPAdapter
adapter = HTTPAdapter(
pool_connections=10, # 连接到不同目标服务器的池子数
pool_maxsize=20, # 每个池子最多保持 20 个连接
)
session.mount('https://', adapter)
Go¶
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接
MaxIdleConnsPerHost: 10, // 每个主机最多保留 10 个空闲连接
IdleConnTimeout: 90 * time.Second,
},
}
Java Apache HttpClient¶
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200); // 总连接池大小
cm.setDefaultMaxPerRoute(20); // 每个目标服务器最多 20 个连接
常见陷阱¶
连接池打满导致延迟飙升
池子大小为 10,但同时来了 50 个请求,40 个在排队等连接。解决方案:合理调大池子,或用 HTTP/2 多路复用减少对连接数的需求。
僵尸连接
服务器端已经关闭了连接,但客户端不知道,下次复用时发现连接已死 → 报错。解决方案:开启连接健康检查、设置合理的空闲超时、使用 TCP keepalive。
练习题¶
连接池大小设为多少合适?
答案
没有万能值,取决于场景。一般经验值:最大连接数 = 服务端能承受的并发数,空闲连接数 = 平均并发请求数的 20-50%。高 IO 等待的场景(如调外部 API)可以设大一些,CPU 密集的场景设小一些。监控连接池的借出/归还速率来动态调整。
HTTP/2 的多路复用还需要连接池吗?
答案
连接池仍然有用。HTTP/2 解决了单个连接上的并行问题,但连接池提供了**容错**(一个连接挂了可以切到另一个)和**负载均衡**(多连接分散压力)的能力。此外,Go 的 http.Transport 本身就是连接池实现。