Cloudflare Queues 消息处理失败后会怎样?

我在本地把 max_retries 设成 2、retry_delay 设成 1 秒,让一条消息每次都调用 retry()。它一共被投递了 3 次,attempts 依次是 1、2、3,间隔约 2 秒,然后进了死信队列。

所以答案是:消息会按 max_retries 重试,重试用完后进入死信队列;没配死信队列,就会被永久删除。 注意 max_retries: 2 在我本地对应的是总共 3 次投递。

重试、死信各有几个细节容易踩,官方文档在其中一个地方措辞还不一致。下面按处理失败后发生的顺序讲。

重试是怎么配置的?

配置里 max_batch_size、max_batch_timeout、max_retries、retry_delay、dead_letter_queue 都是消费者的字段:

{
  "name": "jobs-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "queues": {
    "producers": [{ "queue": "jobs", "binding": "JOBS" }],
    "consumers": [
      {
        "queue": "jobs",
        "max_batch_size": 10,
        "max_batch_timeout": 5,
        "max_retries": 3,
        "retry_delay": 30,
        "dead_letter_queue": "jobs-dlq",
        "max_concurrency": 5
      }
    ]
  }
}

官方文档给的默认值和范围是:max_batch_size 默认 10,上限 100;max_batch_timeout 默认 5 秒,上限 60;max_retries 默认 3,上限 100。批量大小和超时先到先触发。

max_retries 到底对应几次投递?

我本地的结果是:max_retries: 2 对应总共 3 次投递,也就是总尝试次数是它加一。

官方文档在这一点上措辞并不一致:配置页写的是重试三次后标记为失败,而 pull 消费者一节写的是 attempts 达到 max_retries 时消息被删除。两种读法差一次。我没法替官方裁决,线上以官方为准。写业务逻辑时不要依赖精确的总次数,只依赖“最终会进入死信队列或被删除”。

想要退避重试,怎么写?

用 msg.attempts 算延迟,再传给 retry()。attempts 在我本地从 1 开始。

export default {
  async queue(batch: MessageBatch<{ orderId: string }>, env: Env) {
    for (const msg of batch.messages) {
      try {
        await handle(msg.body, msg.id, env);
        msg.ack();
      } catch (e) {
        // delaySeconds 官方最大 24 小时,这里自己封顶 1 小时
        msg.retry({ delaySeconds: Math.min(2 ** msg.attempts * 10, 3600) });
      }
    }
  },
} satisfies ExportedHandler<Env>;

逐条 ack() 和 retry(),让处理器正常返回。如果靠抛异常表达失败,一批里的好消息会被连带重放,原因见 Cloudflare Queues 会重复投递消息吗?。

重试用完后,消息去哪了?

默认是被永久删除。配置了 dead_letter_queue 才会转入该队列。死信队列本身就是一个普通队列,可以单独消费。

官方文档里几个细节:

  • 死信队列必须与源队列在同一个 jurisdiction。
  • 名字不存在时会自动创建。
  • 没有消费者的死信队列里,消息保留四天后删除。
  • 每次重试按一次读操作计费,进入死信队列算一次写入。

我在本地看到,死信队列的消费者收到这条消息时,attempts 重新从 1 开始。

怎么让死信队列真正有用?

死信队列不是垃圾桶,是告警入口。给它配一个消费者,至少记录并报警。没有消费者的话,消息四天后会无声消失,你连它存在过都不知道。

另外,官方文档说的重试间隔和调度细节,本地 wrangler dev 里没法复现,上线前别拿本地的耗时当依据。

本地部分是我在 wrangler dev 里跑的:max_retries: 2 总共投递 3 次、attempts 为 1、2、3、间隔约 2 秒、死信队列里 attempts 重新从 1 开始。默认值与范围、死信队列的 jurisdiction、自动创建、保留四天和计费,都是官方文档的说法,我没有在线上验证;线上真实的重试间隔与调度,本地也无法复现。官方文档在重试次数上的措辞不一致,以官方为准。限额与默认值会变化,以官方文档为准。