典型表现是 requests 复用空闲连接时因服务端主动断连而抛出 connectionerror 或 readtimeout;根本原因是 urllib3 连接池懒惰健康检查且不主动探测远端关闭;推荐三步修复:配置带验证的 poolmanager、复用前手动检测 socket、搭配 retry 自动重试。

Flask 应用里 requests 长连接超时断开的典型表现
你看到的不是 Flask 本身断连,而是底层 requests(或 urllib3)复用连接时,服务端主动关闭了空闲连接,而客户端没及时感知,下次复用时抛出 ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) 或 ReadTimeout。这不是 Flask 的 bug,是 HTTP/1.1 Keep-Alive 的常见副作用。
为什么默认 requests.Session() 无法自动处理服务端强制断连
requests.Session() 默认启用连接池(urllib3.PoolManager),但它的健康检查是“懒惰”的:只在取连接时做简单存活判断,不主动探测远端是否已关闭。一旦服务端(如 Nginx、API 网关)设置了 keepalive_timeout 60s,而你的请求间隔超过这个值,下一次复用就会失败。
- 连接池不会在空闲时 ping 远端,也不重试失败连接
-
max_retries默认为 0(除非显式配置Retry),所以单次失败就直接抛异常 - 即使设了
timeout,它只控制读写阶段,不控制连接复用前的可用性验证
实战中推荐的三步修复方案
不用重写 HTTP 客户端,只需调整连接池行为 + 健康检查策略:
- 给
Session配置带连接复用验证的PoolManager:设置pool_connections=10、pool_maxsize=10,并启用assert_hostname=False(若对接内网自签名证书) - 强制连接池在复用前做轻量级探测:通过
urllib3.util.connection.is_connection_dropped()手动检查 socket 是否已关闭,再决定是否重试 - 搭配
urllib3.Retry实现幂等接口的自动重试:from urllib3.util.retry import Retry<br>retry_strategy = Retry(<br> total=3,<br> backoff_factor=0.3,<br> status_forcelist=[429, 500, 502, 503, 504],<br> allowed_methods=["HEAD", "GET", "OPTIONS", "POST"]<br>)
Flask 全局 Session 初始化时容易漏掉的关键点
很多人把 Session() 放在模块顶层,以为能复用,但没考虑多进程/多线程下连接池共享风险。正确做法是用 Flask 的 app.teardown_appcontext 或依赖注入(如 flask-injector)管理生命周期,或者更稳妥地——每个请求用独立 Session 实例,但必须显式关闭:
- 不要在全局作用域创建长期存活的
Session;它内部的PoolManager在 fork 后可能损坏 - 若必须复用,用
werkzeug.local.LocalProxy绑定到 request context,并确保session.close()在响应后调用 - 特别注意 Gunicorn 的
preload=True模式:此时模块级Session会在 worker fork 前初始化,导致所有子进程共享同一套 socket 句柄 —— 必须禁用 preload 或改用 per-request 初始化
真正麻烦的从来不是超时本身,而是连接池状态与服务端实际连接生命周期的错位。盯住 urllib3 的 _get_conn 和 _put_conn 行为,比调大 timeout 更有效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











