Celery中self.retry()默认countdown=0导致立即重试,必须显式设置countdown实现指数退避,如countdown=2**self.request.retries,并配合抖动、上限及异常类型限定,协程任务需用tenacity替代。

重试不是加个 self.retry() 就完事——不传 countdown 就等于立刻重试,大概率把下游打挂。
为什么 self.retry() 一调就疯狂重试?
Celery 的 self.retry() 默认 countdown=0,任务失败后直接塞回队列头部,毫无等待。网络抖动时,10 个并发任务全在 1 秒内重试 3 次,下游服务瞬间被压垮。
- 必须显式传
countdown参数,哪怕只写countdown=1 -
self.retry()单独调用 ≠ 指数退避,它只是“再试一次”,不带任何延迟逻辑 - 没加
bind=True的 task 里根本拿不到self,更别提self.request.retries
怎么写真正的指数退避?
手动算 countdown 是最可控的方式,尤其适合要加抖动、设上限、或按错误类型差异化处理的场景。
- 基础公式:
countdown = 2 ** self.request.retries(第 1 次失败后等 1s,第 2 次等 2s,第 3 次等 4s) - 加随机抖动防雪崩:
countdown = (2 ** self.request.retries) + random.uniform(0, 1) - 强制上限防失控:
countdown = min(2 ** self.request.retries, 60)(最多等 60 秒) - 必须用
raise self.retry(countdown=countdown, exc=exc),只写self.retry(...)不抛异常,日志里看不到原始错误
autoretry_for 和 retry_backoff 能不能省事?
能,但只适用于简单场景:固定异常类型 + 固定退避基线 + 不需要动态干预。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
-
autoretry_for=(requests.RequestException,)只捕获指定异常,ValueError这类编程错误不会触发重试 -
retry_backoff=2表示第 n 次重试延迟2 ** n秒,不是“每次加 2 秒” -
retry_jitter=True(默认开启)会自动加随机偏移,避免重试时间扎堆 -
max_retries=4是硬上限,超了就抛MaxRetriesExceededError,不再入队
异步任务(async def)千万别用 self.retry()
Celery 原生不支持 await 重试逻辑,self.retry() 在协程函数里会直接报 TypeError: object can't be used in 'await' expression。
- 改用
tenacity:装pip install tenacity,然后在函数上加装饰器 - 示例:
@retry(wait=wait_exponential(multiplier=1, min=1, max=60), stop=stop_after_attempt(3), retry=retry_if_exception_type((ConnectionError, asyncio.TimeoutError))) -
min/max是 jitter 后的最终等待区间,不是指数计算前的原始值 - 务必限定
retry_if_exception_type,否则ValueError也会被反复重试
最容易被忽略的一点:所有重试逻辑必须封装在 @shared_task 函数体内,视图或模型里写循环+time.sleep 是在阻塞请求线程,不是异步;数据库写入前没做幂等校验,重试可能造成重复发邮件、重复扣款——这些都不是重试机制能兜住的问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










