事情本身不复杂,线索都在日志里。Celery 5.4 用 Redis 做 broker,任务设了 acks_late=True,worker 被 OOM kill 后重启,同一批 300 条任务又执行了一遍,下游重复发了 300 条短信,任务平均耗时 40 秒。

第一反应是「visibility_timeout 小于任务执行时间」,验证下来不成立。

排查方向其实有限,先把可能性排一排。acks_late 让消息在任务完成后才 ack,进程被杀就重投。少数情况下是任务本身没有幂等键,重复执行无法识别。

还有一种情况,worker_prefetch_multiplier 默认 4,预取的消息一起回队。

动手时先看 worker 的预取和可见性配置

celery -A app inspect conf | grep -E 'prefetch|visibility'

然后把 visibility_timeout 调到大于最长任务耗时

python -c "print({'visibility_timeout': 7200, 'worker_prefetch_multiplier': 1})"

给任务加基于业务单号的幂等键

python -c "print({'task_id': 'sms:' + order_no})"

模拟 kill worker 后核对短信条数

celery -A app control shutdown && grep -c 'sms sent' worker.log

怎么算真的好了,看的是同一批现象。重复投递的任务因为幂等键被跳过,短信只发一条,worker 重启后队列里的消息最终都被 ack。

补一句。acks_late 提升的是可靠性,代价是至少一次投递;没有幂等键就不能开,或者把可见性超时调到大于任务最长执行时间。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。

作者 9823189