bigywater.tech
爬虫工程 · 3

连接池大小:pool_maxsize 不够时并发线程会发生什么

requests 的 pool_maxsize 经常被误解成“最大并发”。这篇用 30 个线程共用一个 Session,看服务器同时接了多少条连接、连接池里到底留住了多少,以及 pool_maxsize、pool_block、pool_connections 各管什么。

30 / 10
30 并发请求时服务器的并发峰值;默认池容量 10,并不拦着你
50 条
默认池容量下连续两波 30 并发累计开出的连接(池足够大时只有 30 条)
623 ms
pool_block=True 时 30 个请求排队跑完,服务器峰值只有 10

pool_maxsize 不是并发上限

实测 · 本机执行输出
  默认适配器:pool_connections=10 pool_maxsize=10 pool_block=False
  第 1 波(30 个并发请求):耗时 220 ms;服务器同时在处理的请求峰值 30;累计接受 30 条连接
  urllib3 的 "Connection pool is full" 警告 20 条;第一条原文:Connection pool is full, discarding connection: 127.0.0.1. Connection pool size: 10

默认的 HTTPAdapter 是 pool_connections=10、pool_maxsize=10、pool_block=False。池里没有空闲连接时,urllib3 照样新建一条;用完后如果池里已经有 10 条,就把多出来的连接关掉并记一条 WARNING:Connection pool is full, discarding connection。所以 30 个线程能同时跑出 30 条连接。

被丢弃的连接要重建

实测 · 本机执行输出
  默认 pool_maxsize=10:第 1 波累计 30 条连接(222 ms,警告 20 条);第 2 波累计 50 条连接,新增 20 条(217 ms,累计警告 40 条)
  pool_maxsize=30:第 1 波累计 30 条连接(218 ms,警告 0 条);第 2 波累计 30 条连接,新增 0 条(213 ms,累计警告 0 条)

多出来的连接不是免费的。第二波 30 个并发来的时候,池里只剩 10 条,另外 20 条要重新建。把 pool_maxsize 调到不小于线程数,第二波就能全部复用。日志里反复出现“pool is full”警告,就是在提示这件事。

用 pool_block 限制并发

实测 · 本机执行输出
  pool_maxsize=10, pool_block=True,30 个并发:耗时 623 ms;服务器同时处理的峰值 10;累计 10 条连接;警告 0 条
  对照:30 个请求每个 200 ms,如果最多只能 10 个并发,理论下限是 3 批 × 200 = 600 ms
  对照 pool_maxsize=30:耗时 233 ms;服务器同时处理的峰值 30;累计 30 条连接

想让池真的限制并发,要设 pool_block=True:池里没有可用连接时线程等待,服务器的并发峰值就被压到 pool_maxsize。实测 30 个请求花了 623 ms,比 3 批 × 200 ms 的理论下限只多 23 ms。代价是线程会被卡住(这里没有配合超时测试等待连接会不会超时)。

pool_connections:缓存几个主机

实测 · 本机执行输出
  pool_connections=1:3 个服务器上累计连接数 [4, 4, 4](共 12 次请求,每个主机 4 次)
  pool_connections=3:3 个服务器上累计连接数 [1, 1, 1](共 12 次请求,每个主机 4 次)

pool_connections 是 PoolManager 缓存的主机池个数。只留 1 个时,在多个主机间交替请求,每次都新建连接;留 3 个就每个主机复用 1 条。抓很多个域名的爬虫要注意这个值——我只测了“交替访问 3 个主机”这一种模式,没有测更多主机时的淘汰顺序。

分步演示

连接池大小
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。30 个线程用 ThreadPoolExecutor 共用一个 Session;服务器每个请求停 0.2 秒,记录同时在处理的请求峰值(并发)和累计接受的连接数;“Connection pool is full”警告通过给 urllib3.connectionpool 日志器挂 Handler 计数;每种配置各跑一遍
    • 没有验证:真实网络下连接建立的实际耗时(本机回环看不出);pool_block=True 时等待连接的超时行为;pool_connections 的具体淘汰顺序(只测了交替访问);aiohttp 等其它库的连接池参数(后面的文章会单独看);HTTPS 连接池(池规则相同,但没有单独验证)
    京ICP备2026067502号-1京公网安备11010502063487号