Python requests 没设超时:一次任务队列卡住的排查和修复
凌晨跑的账单同步任务突然卡得离谱,队列监控曲线蹭蹭往上涨,查了下worker进程没崩,CPU占用也很低,翻日志发现所有记录全停在「正在请求供应商接口」那一行。折腾半天才找到根因,说出来特别小:Python 里的 requests.get() 没设 timeout,某次网络请求直接卡死之后,整个worker就彻底悬在那占着位置不释放,后面排着的任务全得原地等。
requests不会主动帮你设置请求超时,跑生产环境的脚本必须手动把timeout显式传进去。- 超时最好拆成连接超时和读取超时两部分,比如
timeout=(3, 10),别图省事只写一个特别大的数字糊弄过去。 - 任务队列里要把「能重试的网络临时错误」和「业务侧直接失败」区分开,不然外部接口随便抖一下,就直接把问题放大成整个队列雪崩。
- 修完问题别只跑个单任务看能不能跑完,要盯着队列堆积量、worker占用率、接口耗时分布、失败重试次数这几个指标核对才稳。
- 队列为什么先堆起来
- 从日志时间线定位卡点
- requests 默认超时为什么危险
- 怎么给连接和读取分别设超时
- 重试策略别把故障放大
- 上线前的检查清单
- 常见问题
队列为什么先堆起来
这类故障刚冒头的时候,特别像「第三方外部接口变慢」的问题。业务侧的同学看到的是账单到账延迟,监控面板里 billing_sync 队列长度从几十条一路冲到几千条,偏偏worker进程数还好好的没少。最关键的线索是:每个卡在队列里的worker,全停在请求外部HTTP接口的步骤,根本没进到解析响应、写库、给消息发确认这些后续流程里。
这时候别上来就直接扩容worker,扩完了只会让更多进程一起卡在那等死,队列可能短时间看着掉了点,等外部接口状态没恢复,没过多久老问题还会冒出来。先沉下心查单个任务为什么会占着worker不释放。

从日志时间线定位卡点
排查的时候可以把同一个任务的全链路日志按时间先后串起来。下面这个示例里,任务启动后正常拿到了供应商编号,也拼完了请求URL,刚发出去请求之后就再也没往下打日志了。
10:02:11 job=88421 vendor=A start sync 10:02:12 job=88421 vendor=A build url ok 10:02:12 job=88421 vendor=A request remote api # 后面没有 parse response,也没有 ack message
如果你的worker是多进程或者多线程模型,还可以直接拉一下进程栈信息看卡在哪。线上环境不方便随便装调试工具的话,至少在发请求的前后各打一行带任务ID的日志,直接就能区分出程序是卡在发请求前、请求过程中、还是拿到响应之后解析的环节。
| 现象 | 优先怀疑方向 | 下一步操作 |
|---|---|---|
| 发请求前的日志全正常,发完请求之后再也没输出 | HTTP调用直接挂死 | 检查 timeout 和外部接口的实际耗时 |
| 拿到响应之后解析直接报错 | 接口返回的字段或者格式偷偷变了 | 把原始响应存下来补兼容逻辑 |
| 写库环节特别慢 | 索引坏了、行锁等不到或者单次批量写入的数据太大 | 查慢SQL记录和事务覆盖范围 |
requests 默认超时为什么危险
requests 用起来确实顺手,很多图省事的同学直接就写出下面这种代码:
import requests resp = requests.get(api_url) resp.raise_for_status() data = resp.json()
这段代码的问题就在于,完全没告诉程序「最多等多久必须返回」。TCP连接建立慢、服务端拿到请求之后迟迟不回包、网络中间设备丢包没回应,随便哪种情况都能让这个请求永远占住当前的worker。如果只是本地跑个临时脚本,卡住几分钟顶多就是人等得烦;但放到任务队列场景里,一个worker卡死就等于少了一个处理槽位,十几个worker接连卡死,队列里的任务量直接就滚雪球越堆越多。
这里的修复思路不是把超时时间设得越短越好,而是让最长等待时长和业务的实际容忍度匹配就行。账单同步这种场景可以等得稍微久一点,用户触发的前端请求后面的调用就不能设太长的超时;内网服务调用和公网第三方供应商的接口,也别硬套同一套超时参数。
怎么给连接和读取分别设超时
requests 的 timeout 参数既可以传单个数字,也可以传二元组。跑生产环境更推荐用二元组的写法,前面的数是连接超时时间,后面的数是读取响应的超时时间。
import requests
CONNECT_TIMEOUT = 3
READ_TIMEOUT = 10
def fetch_bill(api_url: str, token: str) -> dict:
resp = requests.get(
api_url,
headers={"Authorization": f"Bearer {token}"},
timeout=(CONNECT_TIMEOUT, READ_TIMEOUT),
)
resp.raise_for_status()
return resp.json()
连接超时可以设得短一点,毕竟连不上对方基本就是网络路径、DNS解析或者目标服务入口出问题了;读取超时可以按照接口平时的实际处理时间适当放宽。别为了省事儿直接写个特别大的数值比如 timeout=120,真出问题的时候worker还是会被占住很久,该拖垮队列还是拖垮队列。

重试策略别把故障放大
给请求加上超时之后,任务会更快失败,但这不代表你可以上来就无脑重试。外部接口本来就已经处理不过来了,你每个失败的请求都立刻重试三次,等于直接把打过去的请求量翻三倍,第三方接口会更卡,本地队列的堆积情况反而更难恢复。
比较稳妥的写法是:只对连接超时、读取超时、临时5xx错误做次数有限的重试;遇到4xx错误、参数不对、鉴权失败这类业务层面的问题直接标记失败就行不用重试。重试的时候顺便加一点退避间隔,别让所有失败任务同一时间又一窝蜂往对方接口打。
import time
import requests
RETRYABLE_STATUS = {502, 503, 504}
def fetch_with_retry(api_url: str, token: str, retries: int = 2) -> dict:
last_error = None
for attempt in range(retries + 1):
try:
resp = requests.get(
api_url,
headers={"Authorization": f"Bearer {token}"},
timeout=(3, 10),
)
if resp.status_code in RETRYABLE_STATUS:
raise RuntimeError(f"temporary status {resp.status_code}")
resp.raise_for_status()
return resp.json()
except (requests.Timeout, RuntimeError) as err:
last_error = err
if attempt == retries:
break
time.sleep(0.5 * (attempt + 1))
raise RuntimeError(f"remote api still unavailable: {last_error}")
上面的代码只演示了核心的判断逻辑。实际项目里完全可以把重试逻辑、日志打印、指标上报这些功能封装成一个统一的小HTTP客户端,不用每个任务函数都自己手写一遍。
上线前的检查清单
修复这类问题,把代码改完只是第一步,上线之前建议至少核对完下面这些点:
- 所有发外部HTTP请求的地方都显式配置了
timeout,包括requests.get()的get、post还有大文件下载的场景。 - 日志里能明确看到任务ID、供应商编号、请求实际耗时、返回状态码和重试次数这些关键字段。
- 监控面板里已经配好了队列长度、单任务平均耗时、超时次数、重试次数和失败原因这些指标的告警。
- 超时失败之后的消息处理规则很明确,是直接确认掉、放回队列重投还是丢去死信队列,别含糊不清。
- 等外部接口恢复正常之后,队列的消费速度确实能追上新进来的任务速度,不会一直有堆积。
更推荐先把全项目的公共HTTP客户端收拢统一管控,再逐个替换散落在各个业务脚本里的裸 requests.get() 调用。以后要调整全局超时、加代理、补指标上报这些操作,不用翻几十上百个业务文件挨个改代码。
常见问题
timeout 传单个数字和传二元组到底有啥区别?
传单个数字的时候,连接等待和读取响应这两个环节共用同一个超时时间;传二元组的话可以分别自定义连接和读取的最长等待时长,更适配生产环境的复杂场景。
超时时间应该设成多少比较合适?
先看接口平时正常跑的耗时分布,再结合业务能接受的最长等待时间来定。普通的外部查询类接口可以先用 (3, 10) 这类比较保守的数值起步,后面跑一段时间根据监控数据慢慢调就行,不建议一上来就设几十秒那么长。
所有抛出来的异常都应该重试吗?
肯定不是。连接超时、读取超时、临时5xx错误这种场景可以做有限次数的重试;参数错误、鉴权失败、资源不存在这类问题就算重试多少次也不会成功,直接记录清楚失败原因就行。
只靠任务队列自带的超时设置够不够防卡死?
完全不够。队列层面的超时一般是最后兜底用的,如果你不在HTTP调用这一层自己设超时,worker还是会占着处理槽位挂很久,出问题之后整个链路恢复速度会特别慢。
小结
Python写的任务队列卡住的时候,别光盯着worker数量和机器负载乱调。只要脚本里有发外部HTTP请求的逻辑,先确认 requests 有没有配置明确的 timeout,再核对重试规则、日志完整性和队列的消息确认逻辑。把连接超时、读取超时、有限重试和清晰监控这几点都做到位,哪怕外部接口临时抖一下,影响范围也只会停在局部,不会直接拖垮整条任务链路。
Go 接口防重复提交:用 Idempotency-Key 处理按钮连点和网络重试
- 上一篇
- Go 接口防重复提交:用 Idempotency-Key 处理按钮连点和网络重试
- 下一篇
- Go slog 结构化日志怎么接入:从 fmt 打印到可检索字段
-
- 文章 · python教程 | 1星期前 | logging · Python教程 · 后端开发 · 日志排查 · Python logging 日志重复 propagate addHandler basicConfig
- Python logging 日志重复打印排查:为什么一条记录输出了两遍
- 324浏览 收藏
-
- 文章 · python教程 | 3星期前 | 默认值 · python · 数据建模 · dataclass · default_factory · field · Python 数据类 Field 可变默认值 dataclass default_factory
- Python dataclass 默认值完整工作流:从可变默认值到 default_factory
- 228浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 4368次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4048次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4036次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4219次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4189次使用
-
- Python监控网页状态:requests异常处理实战
- 2026-05-29 501浏览
-
- TensorFlow模型部署为API的TF Serving方法
- 2026-05-26 501浏览
-
- Python字符串编码转换:encode与decode详解
- 2026-05-16 501浏览
-
- TensorFlow裁剪无用算子方法详解
- 2026-05-15 501浏览
-
- httpx 如何设置代理认证(Proxy-Authorization)
- 2026-05-05 501浏览

