连接池溢出本质是连接未归还导致复用机制失效,而非单纯并发过高;requests多线程下因每个Session独占池、未关闭连接或未统一配置适配器引发资源浪费,aiohttp中循环创建ClientSession同样破坏复用,Flask+SQLAlchemy亦因session未显式关闭或pre_ping缺失导致假存活连接堆积。

连接池溢出不是“并发太多”的表象问题,而是连接复用机制被破坏后的必然结果——脚本没关连接,系统就真不还。
requests多线程下HTTPConnectionPool满警告的直接原因
典型日志:WARNING:urllib3.connectionpool:Connection pool is full, discarding connection: example.com。这不是错误,是urllib3在丢弃你没归还的连接。
- 默认
Session只配了pool_connections=10、pool_maxsize=10,10个连接上限,但你开了20个线程,每个都新建requests.Session(),等于造了20个独立池子,每个池子又只允许10个连接,实际资源翻倍浪费 - 没显式调用
session.close(),或线程退出时session对象被丢弃但底层TCP连接未释放,连接卡在ESTABLISHED或TIME_WAIT状态 -
headers = {'Connection': 'close'}看似关闭连接,实则强制退化为短连接,反而加剧端口耗尽和握手开销
aiohttp高并发时ClientSession连接池瞬间打满
现象是aiohttp.client_exceptions.ServerDisconnectedError或asyncio.TimeoutError,根源不在“并发太高”,而在连接生命周期失控。
- 在循环里每次请求都
async with aiohttp.ClientSession() as session:——每个session带一个独立TCPConnector,limit_per_host形同虚设,连接不复用 - 没配
keepalive_timeout,空闲连接一直挂着,池子被无效连接占满;设force_close=True则彻底放弃连接复用 - 没用
asyncio.BoundedSemaphore控并发数,limit=100只是上限,1000个协程同时抢连接,900个在排队,超时后全扔掉
requests重试配置反而加剧连接泄漏
requests.adapters.DEFAULT_RETRIES = 5这类全局设置,对连接池健康毫无帮助,还可能掩盖真实问题。
立即学习“Python免费学习笔记(深入)”;
- 重试是针对单次请求失败的补救,而连接池溢出是资源管理失效——retry次数再多,卡住的连接也不会自动释放
- 把
s = requests.Session()放在循环外是基础,但若没统一管理mount适配器,不同协议(http/https)仍走默认小池子 - 真正该设的是
HTTPAdapter(pool_connections=50, pool_maxsize=50),再session.mount('https://', adapter),否则https请求还是用默认10连接池
Flask+SQLAlchemy里数据库连接池也一样会溢出
Web服务里db.session泄漏和爬虫连接池溢出本质相同:没归还。
-
@app.teardown_request不保险——异常中途退出、CLI命令、异步视图都不会触发;必须用@app.teardown_appcontext并确保db.session.close()执行 -
pool_pre_ping=False时,MySQL断连后连接还在池里“假存活”,后续所有取该连接的请求全卡死,表现就是连接池耗尽 -
max_overflow不是越大越好,设成100意味着MySQL要额外撑住100个连接,每条约15MB内存,容易直接拖垮DB
所有连接池问题最终都落在“谁创建、谁释放、何时释放”这三件事上。工具库不会替你记着连接用完了没,它只管分配——你忘了还,它就真不催。


















