Celery 的 retry 不自动指数退避,因其默认 countdown=0 且无内置退避逻辑;必须显式传入 countdown 参数并结合 self.request.retries 手动实现,如 countdown = 2 ** self.request.retries。

为什么 Celery 的 retry 不自动指数退避?
Celery 默认的 retry 只是立即重试,不带任何延迟逻辑。你调用 self.retry() 却发现任务一秒内连跑五次,不是因为配置漏了,而是它压根没内置退避——得手动算延迟时间。
常见错误现象:MaxRetriesExceededError 比预期早得多;日志里看到重试间隔全是 0s 或固定值;服务端被下游接口瞬间打挂。
- 必须显式传入
countdown参数,否则默认为 0 - 指数退避得自己实现:比如第 1 次等 1s,第 2 次等 2s,第 3 次等 4s……常用公式是
2 ** max_retries_attempted - Django settings 里的
task_acks_late和task_reject_on_worker_lost会影响重试是否真正触发,别只盯 retry 参数
怎么在 task 函数里写安全的指数退避重试?
核心是把重试逻辑收进函数体,用 self.request.retries 获取当前重试次数,再算出下次 delay。别依赖外部状态或全局变量,Celery worker 是无状态的。
@app.task(bind=True, autoretry_for=(requests.RequestException,), retry_kwargs={'max_retries': 3})
def fetch_remote_data(self, url):
try:
return requests.get(url, timeout=5).json()
except requests.RequestException as exc:
countdown = 2 ** self.request.retries
raise self.retry(exc=exc, countdown=countdown)
-
bind=True必须加,否则拿不到self.request -
autoretry_for只负责自动抛出 retry,不控制 delay,delay 还得靠self.retry()显式传 - 如果用了
retry_kwargs,注意它和手动self.retry()共存时,max_retries是叠加判定的,容易误超限
Django 中如何让重试策略可配置、不硬编码?
把退避参数从代码里抽出来,放到 Django settings 或环境变量里,否则改个重试间隔就得发版。
立即学习“Python免费学习笔记(深入)”;
例如在 settings.py 加:
CUSTOM_RETRY_BACKOFF = {
'max_retries': 5,
'base_delay': 1, # 秒
'max_delay': 60, # 上限防雪崩
}
然后 task 里读取:
@app.task(bind=True)
def send_notification(self, user_id):
from django.conf import settings
cfg = settings.CUSTOM_RETRY_BACKOFF
try:
send_email(user_id)
except EmailSendError as exc:
delay = min(cfg['base_delay'] * (2 ** self.request.retries), cfg['max_delay'])
raise self.retry(exc=exc, countdown=delay)
- 一定要设
max_delay,否则第 10 次重试会等 512 秒,完全失去意义 - 别用
time.sleep()替代countdown,那会阻塞 worker 线程,影响并发 - 如果用 Redis 作 broker,确认
visibility_timeout大于最大可能的countdown,否则任务会被重复投递
重试失败后,怎么避免丢数据或重复副作用?
重试本身不解决幂等性问题。HTTP 请求重试可能造成订单重复提交,数据库写入重试可能多插一条记录——这不是 Celery 的锅,是你没设计好任务边界。
- 所有可能重试的任务,必须自带幂等 key,比如用
user_id + timestamp做缓存锁,或写入前先查唯一索引 - 避免在重试逻辑里做“半截操作”:比如先发邮件再写 DB,应改成先写 DB(带 status 字段),再发邮件,失败时只重试发邮件
- 使用
ignore_result=True时,self.retry()仍有效,但结果无法被追踪,调试时容易误判是否真重试了
最常被忽略的一点:重试不是万能兜底。网络超时、数据库连接池满、下游服务彻底不可用——这些场景下,指数退避只会让失败更晚暴露,该告警的得告警,该降级的得降级。


















