事情本身不复杂,线索都在日志里。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 提升的是可靠性,代价是至少一次投递;没有幂等键就不能开,或者把可见性超时调到大于任务最长执行时间。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。
