Celery 死信队列 Dead‑Letter Queue(DLQ)

Celery 死信队列 Dead‑Letter Queue(DLQ)

通俗理解:正常队列放不下、处理不了的消息,丢到死信队列。存放那些确定处理失败、不能再重试的任务消息。

前提:Celery本身没有开箱即用的死信队列,需要借助broker的能力(Redis / RabbitMQ)手动配置。

什么消息会进死信

  1. 任务多次重试,重试次数耗尽,还是失败,不再继续重试。
  2. 消息过期(超过TTL)。
  3. 消息被拒绝,不允许重新入队。
  4. 队列满了,消息无法投递。

正常流程:任务 → 业务队列(work_queue) → worker取出来执行。

失败重试:抛出异常 → 重新放回原队列,反复重试。

重试耗尽:不再放回原业务队列,路由转发到死信队列 dlq。

死信队列里面的消息,不会被普通worker消费 。

需要人工观察、排查问题:参数错误、业务bug、第三方接口挂掉;修复完之后,可以把死信消息重新放回业务队列重新执行。

对比普通失败 vs 死信

  • 普通失败+重试:还抱有希望,放回原队列,等下一次worker拿出来再跑。
  • 死信:已经放弃自动重试,交给人工处理。

不要让消息无限重试!无限重试会疯狂打数据库、打第三方接口,雪崩。所以设置最大重试次数,耗尽就丢死信。

RabbitMQ 和 Redis 的区别

  1. RabbitMQ :原生完整支持死信交换机 DLX,配置简单。

    消息满足条件,通过死信交换机路由到死信队列。

  2. Redis broker(celery默认常用)

    Redis本身没有原生死信队列。

    Celery‑Redis 实现死信,一般两种方案:

  • 方案A:任务重试耗尽之后,手动在 on_failure钩子里面,把任务手动push到另一个独立redis list,作为死信队列。
  • 方案B:使用单独的死信worker,不推荐。

很多人踩坑:用Redis做broker,不配置死信;重试耗尽之后任务直接丢掉,消息直接消失,找不到。

简单代码模拟(Redis场景,手动实现死信)

python 复制代码
from celery import Task
import redis

r = redis.Redis(decode_responses=True)
DEAD_LETTER_QUEUE = "dead_letter_queue"

class BaseTaskWithDLQ(Task):
    autoretry_for = (Exception,)
    retry_backoff = 1
    retry_max = 3  # 最多重试3次,耗尽触发on_failure

    def on_failure(self, exc, task_id, args, kwargs, einfo):
        # 重试耗尽,进入on_failure钩子
        print(f"任务{task_id}彻底失败,送入死信队列,参数 {args}")
        # 手动把任务信息存入死信队列redis list
        r.lpush(DEAD_LETTER_QUEUE, str({
            "task_id": task_id,
            "args": args,
            "kwargs": kwargs,
            "error": str(exc)
        }))

@shared_task(base=BaseTaskWithDLQ)
def test_task(x):
    1 / x

逻辑:

  1. 抛出异常,自动重试,最多3次。
  2. 3次全部失败,进入on_failure钩子。
  3. 在钩子里面把任务信息推到独立redis list,充当死信队列。
  4. 普通worker不会消费这个dead_letter_queue。

死信队列配套运维操作

  1. 定时告警:监控死信队列长度,长度>0就告警,通知开发。
  2. 排查:查看死信里面的任务id、参数、报错。
  3. 修复bug之后,把死信消息重新放回业务队列重新消费。
  4. 清理旧的死信消息。

重要误区

  1. ❌死信队列不会自动重新执行任务。死信是保存失败消息,等待人工介入,不会自己恢复。
  2. ❌on_failure钩子本身不等于死信队列。钩子只是通知;需要你自己把消息存储下来,才形成死信。
  3. ❌Redis broker不会自动把消息丢死信,必须自己实现;RabbitMQ可以通过DLX自动路由。
  4. 如果任务内部捕获异常吞掉异常,不抛出去:不会重试,也不会进死信。

和之前知识点串联

  1. on_failure 钩子:事件回调,只是收到任务彻底失败的通知,没有保存消息的能力;我们利用这个钩子把消息存入死信存储。
  2. 钩子是通知;死信队列是存储失败消息的容器。
  3. 中间件可以在任务执行前拦截;死信是任务已经彻底失败之后的归宿。

通俗比喻

快递派送:

正常任务 = 快递派送。

重试 = 派送失败,第二天再派送一次。

重试多次全部失败 → 快递无法送达,放到无人认领包裹仓库(死信队列) 。

不会反复派送;需要人去仓库查看包裹,确认地址修好之后,再重新派送。

什么时候需要死信队列?

业务场景:任务不能丢,失败之后希望保留现场,不能直接消失。

比如订单回调、数据同步任务;如果不做死信,重试耗尽任务直接丢失,业务数据就缺了。

简单总结:

死信队列就是专门存放重试已经全部失败,不再自动处理的消息的队列;目的是保存现场,告警,留给人工排查和恢复。RabbitMQ原生支持;Redis做broker需要借助on_failure钩子手动实现。

相关推荐
锋行天下8 小时前
langgraph 后端发自定义数据,前端接收方法概括
人工智能·python
benchmark_cc9 小时前
用 QuantDash 读取单只股票五档盘口:抽样、缺失值检查与复现记录
python·数据分析·pandas·量化交易·股票数据·quantdash
曲径海盗10 小时前
【Python 学习第 55 天】列表基础
python·学习笔记·编程入门
专注仿真10 小时前
知识问答模型用到的算法集详解及代码实例
python·算法·代码·大模型训练·控制算法
jyOverQ10 小时前
我写了一个 CSDN Markdown 图片助手:告别本地图片逐张上传
python·工具·csdn
刘马想放假14 小时前
CANviz 使用指南|Linux 安装、DBC 解码、实时曲线与 J1939 实战
linux·python·websocket
2601_9679873714 小时前
python判断变量名不要调用任何函数实现
python·函数·语法·判断·变量名
卷无止境15 小时前
SALib敏感性分析的python库学习笔记
后端·python
duansamve16 小时前
Python3+Flask+PostgreSQL18+Vue3项目生产环境用docker如何部署?
python·docker·flask
特立独行的猫A16 小时前
少 3 个环节、少 1 次 ABI、少 3500 行代码:cj-tauri 与 Ionic/Capacitor 的鸿蒙桥接机制对比
java·后端·python