DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Celery ETA 重复执行

Redis broker的长ETA可能超过可见性超时而重复投递。核对任务时间线,优先持久调度,按版本统一秒数配置并处理幂等。

同一个 Celery task ID 在多个 worker 中反复出现,到了预约时间又执行多次。如果 broker 使用 Redis,任务设置了较长的 eta 或 countdown,先对照等待时长与 visibility_timeout。等待超过消息可见性超时,是这类重复投递的一条明确路径;短时间重复、生产者重复派发和周期计划写错,需要另外查。

下面的配置规则按 Celery 5.6.3 说明。Redis 在这里是 broker;仅把任务结果存入 Redis,不会让其他 broker 改用它的消息投递规则。

ETA 任务会先被 worker 取走

eta 不是“到点才从 Redis 读取”。worker 可以立即取得任务,将它留在内存中等待,直到开始执行才确认这条预约任务。

Redis broker 的默认可见性超时为一小时。超过超时仍未确认,消息可能重新投递给另一个 worker;原 worker 中的预约任务却仍可能存在。到了 ETA,多个已预留的副本可能先后或并行执行。这个等待阶段不要求你启用 task_acks_late 才会出现。

为同一任务关联记录派发时间、接收时间、ETA、worker 标识和开始执行时间,检查重复接收是否与可见性超时吻合。不要只看结果后端最终留存的一条成功状态:它不能证明业务副作用只发生一次。

如果 producer 本来就发了两次,或者 Beat 的分钟字段写成 *,增加超时不会修复重复派发。worker 强制终止、连接异常和任务自身重试,也要从各自的日志顺序判断。

长期预约放到可持久保存的计划中

对于明天、下周或更远的任务,把计划及业务标识保存在数据库,由调度器接近执行时间时派发,通常比让 worker 长时间持有 ETA 消息更合适。使用 Django 时,可评估数据库支持的调度器;仍要按业务要求设计到期查询、派发状态、失败重试和幂等。

大量远期 ETA 消息还会占用 worker 内存。单纯提高预取限制或减少并发,并不能消除“等待超过确认期限”这一条件。

短期等待确需扩大预算时,保持配置一致

先确定业务允许的最大等待时间、确认时机和故障恢复时限。以下 7200 仅示范两小时的秒数,不是推荐所有任务使用两小时;只适用于经过评估的有界等待,实际值应留出合理余量。

timeout_seconds = 7200

app.conf.broker_transport_options = {
    **app.conf.broker_transport_options,
    "visibility_timeout": timeout_seconds,
}
app.conf.result_backend_transport_options = {
    **app.conf.result_backend_transport_options,
    "visibility_timeout": timeout_seconds,
}
app.conf.visibility_timeout = timeout_seconds

这里的 app 是项目已有的 Celery 实例。保留原有 transport 配置,不要用示例覆盖 Sentinel、重试或其他选项。Celery 5.6.3 要求这三个设置使用同一个值;共享 broker 的多个应用还要一并核对,较短的值可能生效,包括未显式配置而发送的默认值。

扩大超时会延后部分故障后的重新投递。worker 断电或被强制杀死,来不及归还消息时,其他 worker 可能要等更久;不能把超时无限放大,或以关闭确认模拟来换取表面上的不重复。

用业务结果验收重复处理

在隔离队列覆盖预约等待、worker 正常关闭、强制退出和重启,观察任务接收与业务完成记录。修正配置不会撤回已留在 worker 中的副本;上线切换要盘点已有预约任务,按业务标识处置,避免直接清队列丢失计划。

扣款、发信等操作需要独立的幂等约束。简单的“先查缓存、再写缓存”存在并发空隙,先标记完成再执行业务又可能在崩溃后跳过未完成操作。用业务唯一标识、可恢复的状态及适当的原子约束判断是否已经完成,而不是把一条 task ID 的成功日志当作只执行一次的保证。