直接用requests.post发webhook会出问题,因为默认同步阻塞,一个失败就卡住后续分发;超时、网络抖动或目标服务临时不可用都会中断流程,且主线程无法安全等待重试,易拖垮服务响应能力。

为什么直接用 requests.post 发 Webhook 会出问题
因为默认同步阻塞,一个失败就卡住后续分发;超时、网络抖动、目标服务临时不可用都会导致整个流程中断。更麻烦的是,你没法在主线程里等 30 秒重试三次——这会拖垮整个服务响应能力。
实操建议:
- 绝不要在 Flask/FastAPI 的请求处理函数里直接调用
requests.post并设timeout=(3, 10)就完事 - 必须把 Webhook 发送剥离到异步任务中,且任务自身要带状态追踪和退避逻辑
- 重试不是“失败就再发一次”,要区分错误类型:
ConnectionError可重试,400 Bad Request再发也没用
用 asyncio + aiohttp 实现非阻塞分发
aiohttp 是目前 Python 中最稳妥的异步 HTTP 客户端,比 httpx 在高并发 Webhook 场景下更可控(尤其对连接池复用和 DNS 缓存)。
关键点:
- 复用同一个
aiohttp.ClientSession实例,避免反复创建 TCP 连接 - 设置
raise_for_status=True,让 4xx/5xx 自动抛异常,便于统一捕获 - 超时必须显式指定:
timeout=aiohttp.ClientTimeout(total=15, connect=5),否则默认无限制
示例片段:
async def deliver_webhook(session: aiohttp.ClientSession, url: str, payload: dict):
try:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=15)) as resp:
resp.raise_for_status()
return {"status": "success", "code": resp.status}
except aiohttp.ClientConnectorError:
return {"status": "failed", "reason": "connect_timeout"}
except asyncio.TimeoutError:
return {"status": "failed", "reason": "request_timeout"}
except Exception as e:
return {"status": "failed", "reason": f"unexpected: {type(e).__name__}"}
重试策略不能只靠 time.sleep 硬等
固定间隔重试(比如每次等 2 秒)在真实场景中极易引发雪崩:所有失败请求在同一时刻重试,压垮下游。必须用指数退避 + 随机抖动。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
实操建议:
- 用
asyncio.sleep(2 ** attempt + random.uniform(0, 1))替代time.sleep(2) - 最大重试次数建议设为 3~5 次,超过就进死信队列或告警,别无限循环
- 记录每次重试的
attempt、backoff_delay、error_type,方便排查是网络问题还是业务错误 - 注意:重试逻辑必须包裹在异步任务内部,不能在上层协程里用
for循环 +await—— 这样会阻塞同批次其他 Webhook
Webhook 任务状态怎么持久化才不丢
内存里存任务状态(比如用 dict 记录是否成功)在进程重启后全丢。但又没必要一上来就上 Redis 或数据库——多数中小项目只需要“至少投递一次”+ “可人工补发”即可。
轻量方案:
- 用 SQLite 做本地持久化,每条 Webhook 记录含:
id、url、payload、status(pending/sent/failed)、attempts、last_error、created_at - 发送前先
INSERT OR IGNORE,成功后UPDATE status = 'sent',失败则UPDATE attempts = attempts + 1 - 加个后台定时任务(比如每分钟查一次
status = 'failed' AND attempts ),重新触发异步发送
注意:SQLite 的 WAL 模式要开启,否则并发写入可能锁表;别用 autocommit=False 手动 commit,容易漏提交。
真正难的不是写重试逻辑,而是判断哪次失败该重试、哪次该立刻告警、哪次该人工介入——这些边界得靠线上错误日志反推,没法靠预设规则穷尽。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










