可靠性#

一条消息投出去之后可能执行失败或超时。这一页说明两类情形的处置: 失败重试,以及尚未落地的死信队列(DLQ)。它们在链路中的位置见 架构。

尝试次数#

Message 带一个 attempt 字段(默认 1),它随 payload 一起序列化,因此跨进程、 跨重投都跟着消息走。每投递一次算一次尝试:

  • 客户端首次投递:attempt = 1;

  • Worker 执行失败或超时:attempt += 1,未超出上限则退避后重投;

  • reclaim 把滞留消息重投给新消费者时同样 attempt += 1。

失败重试#

任务抛异常、或执行时间超出 task_timeout 时,Worker 按以下顺序处理:

  1. 记一条失败日志;

  2. attempt += 1;

  3. 若 attempt <= max_attempts(默认 3),按退避延时把消息重新投给队列 —— 这次尝试在队列里是一条新 entry;

  4. 若超出上限,记 max attempts reached 并丢弃该消息(当前止于日志,见下文 DLQ)。

消息在队列里滞留超过 task_timeout(即 enqueued_at 太旧)时走同一条重投路径:它不会 被执行,而是带着新的时间戳重新入队。

from binomic.config import BinomicConfig

# 单条消息最多尝试 3 次(首次 + 2 次重投)
config = BinomicConfig(queues=["default"], max_attempts=3)

备注

重投不是立即发生的:Worker 按退避延时投递,首跳 1.5 秒,此后逐次翻倍 (3 / 6 / 12 / 24 秒),封顶 30 秒。退避由 Worker 自己持有的调度器承担 —— 它把 「重新入队」排成一个延时作业,而原件在排程之后就已经 ack。由此有两个后果:

  • 退避窗口内 Worker 进程若退出,该次重试不会发生 —— 换来的代价是绝不重复投递;

  • 该延时作业被排在「迟到也执行」的位置上,不会因为事件循环繁忙而被静默跳过。

退避间隔目前是固定值,不可配置;需要别的节奏时,请在任务函数内部自行等待。

有两类失败不重试,只记日志并 ack(消息被丢弃)—— 重投同样的 payload 不会改变结果:

  • 消息无法反序列化(payload 损坏);

  • 任务名未注册(TaskNotFoundError)。

取消不 ack:Worker 关闭或任务被取消时,消息留在 PEL 里而不是被 ack,交给下一次 reclaim 重投给其它消费者。

死信队列(尚未实现)#

max_attempts 耗尽,以及上面两类不重试的失败,目前都只记日志然后丢弃;代码中留有 TODO: send to DLQ 标记。在 DLQ 落地之前,这些消息不可恢复 —— 需要保留时,请在任务 函数内部自行捕获异常并落盘或另投队列。