Python requests 超时总设不对?从 timeout、重试到连接复用的稳定写法
调用第三方接口时,最常见的线上故障不是逻辑写错,而是请求没有合理超时:一个接口 hang 住,整个线程池就被拖垮。下面是一套可以直接落地的写法,核心是三件事:超时要分开设、失败要可重试、连接要复用。
一、timeout 不要只写一个数
requests 的 timeout 参数可以传一个数,也可以传一个二元组,分别控制连接超时和读取超时。只写 timeout=10 时,连接和读取共用 10 秒;写成 timeout=(3, 10) 则含义更清楚:3 秒内连不上就放弃,连接成功后 10 秒内读不到数据也放弃。内网服务可以把连接超时放得更短,比如 (2, 5);跨公网调用下游则读取超时要留足,比如 (3, 30)。关键是:永远不要不写 timeout,默认不设超时意味着可能无限等待。
二、区分哪些异常可以重试
超时和连接异常通常可以重试,但要先区分异常类型。ConnectTimeout 说明连不上对方,ReadTimeout 说明连接建立后对方迟迟不返回,ConnectionError 可能是网络抖动或对方端口不可达。重试时必须确认接口是否幂等:查询类 GET 请求可以放心重试;下单、扣款、发短信这类非幂等 POST,盲目重试可能造成重复下单,必须先由业务侧保证幂等键或先查后写,再谈重试。
三、用 Session 做连接复用
每次调用 requests.get 都会新建连接,高频调用时握手开销很大。正确做法是复用一个 Session 对象:同一个 Session 会复用底层的 TCP 连接,配合连接池参数可以控制并发。示例思路是:创建一个 Session,把总连接池设为 20、单 host 上限设为 10;同一进程内的多次请求都走这个 Session;程序退出前调用 close 释放连接。注意 Session 不是线程安全的,多线程场景下每个线程应持有自己的 Session,或者用线程本地存储隔离。
四、重试要加退避,不要立刻连打
重试策略建议用 urllib3 的 Retry 挂载到 Session 的 HTTPAdapter 上:总重试 3 次,只对连接错误和 502、503、504 这类服务端临时错误重试,退避因子设为 0.5,实际等待时间大约是 0 秒、1 秒、2 秒递增。这样既给了对方恢复时间,也避免重试风暴把对方打垮。不要对 400、401、403、404 做重试,这些是请求本身有问题,重试多少次结果都一样。重试次数和退避时间要写进日志,方便事后排查到底是第几次重试成功,还是最终失败。
五、超时异常要分开捕获和记录
不要用一个笼统的 except Exception 把所有异常吞掉。建议分开处理:ConnectTimeout 记录目标地址和连接耗时,ReadTimeout 记录接口路径和已等待时间,TooManyRedirects 说明重定向环路,SSLError 往往是证书或协议问题。日志里至少包含接口地址、超时配置、重试次数和最终耗时,缺了任何一项,线上定位都会多花一倍时间。
六、上线前自查清单
第一,所有 requests 调用都有 timeout,且是二元组形式;第二,非幂等接口没有无脑重试;第三,高频调用走了 Session 复用;第四,重试只覆盖临时性错误并带退避;第五,超时日志包含地址、配置和耗时。把这五条过一遍,大部分“接口偶尔卡死”“高峰期线程池打满”的问题都能提前拦住。
- 点赞
- 收藏
- 关注作者
评论(0)