requests请求超时必须显式设置,推荐用(timeout_connect, timeout_read)元组如(3,10),避免默认不设超时导致卡死;底层urllib3连接池和aiohttp异步超时需单独配置,全局设timeout风险大。

requests 请求超时必须显式设置 timeout
Python 的 requests 默认不设超时,卡住就真卡住——比如 DNS 解析失败、服务器不响应、网络中断,requests.get() 可能挂十几分钟不动。这不是 bug,是设计选择,但生产环境绝对不能依赖它。
实操建议:
-
timeout必须传,且推荐拆成(connect_timeout, read_timeout)元组,比如timeout=(3, 10):3 秒内完成 TCP 连接(含 DNS),10 秒内收完响应体 - 只传单个数字(如
timeout=5)等价于(5, 5),对慢接口或大响应不友好 - 设太短(如
(0.5, 1))容易误杀正常请求;设太长(如(30, 60))会拖垮整体响应延迟,尤其在并发场景下 - 注意:超时抛出的是
requests.exceptions.Timeout,不是Exception,捕获时别用裸except:
urllib3 底层连接池的 timeout 配置常被忽略
requests 底层用 urllib3,而连接池(PoolManager)自己也有一套超时逻辑。如果你手动复用 session 或自定义 adapter,这里容易漏配。
常见错误现象:设置了 timeout,但某些请求仍长时间阻塞,尤其在高并发复用连接时。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 若用
requests.Session(),可通过session.mount('https://', requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=20))控制连接池大小,但真正影响超时的是urllib3的PoolManager参数 - 要精细控制底层行为,可直接构造
urllib3.PoolManager,传入timeout=urllib3.util.Timeout(connect=3, read=10) - 默认情况下,
urllib3的timeout是None,即不生效;只有上层requests的timeout参数才会透传并覆盖它
异步请求(aiohttp)的 timeout 机制完全不同
aiohttp 不是 requests 的异步版,它的超时是基于 asyncio event loop 的,配置位置和语义都不同,混用会出问题。
使用场景:需要并发发几百个请求,又不想被某个慢接口拖死整个协程。
实操建议:
-
aiohttp.ClientSession的timeout参数类型是aiohttp.ClientTimeout,必须显式构造,比如ClientTimeout(total=15, connect=3, sock_read=10) -
total是整个请求生命周期上限,会强制 cancel 协程;connect和sock_read是子阶段超时,更细粒度 - 注意:如果没设
total,仅设connect或sock_read,一旦 DNS 卡住或服务端迟迟不发响应头,协程仍可能 hang 住 - 错误信息如
asyncio.TimeoutError或aiohttp.ServerTimeoutError,需分别捕获
全局默认 timeout 容易掩盖问题
有人想“一劳永逸”,在 requests.adapters.DEFAULT_TIMEOUT 上改值,或 monkey patch requests.request。这看似省事,实际埋雷。
为什么这样做风险大:
- 第三方库(如
botocore、elasticsearch-py)内部也用requests,你的全局改动会影响它们的行为,导致不可预期的失败 - 不同接口对延迟容忍度差异极大:查缓存可以 100ms 超时,调下游支付网关可能要留 5s,统一设一个值等于放弃治理
- 测试环境 mock 接口响应快,上线后才暴露超时不合理,问题滞后
真正该做的是:每个业务请求点明确声明 timeout,配合监控看 requests.exceptions.Timeout 出现频次和分布,再反推调优。
最麻烦的点往往不在 timeout 值本身,而在超时之后要不要重试、重试几次、间隔怎么设——这部分逻辑必须和具体业务状态耦合,没法通用化。










