先把当时的情况摆出来。Celery 5.3 配了 4 个 worker 消费同一个队列,一条 40 秒的录音转写任务在日志里跑了 4 遍,broker 是 Redis,visibility_timeout 还是默认的 3600。
排查方向其实有限,先把可能性排一排。任务没开 acks_late,worker 崩溃后消息被重新投递。还有一种是broker_transport_options 里的 visibility_timeout 与任务时长不匹配。
另外,多个 worker 共用队列名但没有做幂等。少数情况下是prefetch_multiplier 默认 4,一个 worker 先抓走四条。
第一件事是把关键配置打印出来核对
python -c "from celery import Celery;app=Celery('t');app.conf.update(task_acks_late=True,worker_prefetch_multiplier=1);print(app.conf.task_acks_late,app.conf.worker_prefetch_multiplier)"
给任务加一个 Redis 幂等锁
redis-cli SET lock:job:8821 1 NX EX 600
然后把 visibility_timeout 调到大于最长任务时长
redis-cli CONFIG SET visibility_timeout 7200
等前面都确认过,再重启 worker 后观察同一条任务的出现次数
celery -A tasks inspect active
中间停过一次,把已经改的东西回滚干净,再重新来一遍。
恢复的判定其实很直接。同一条任务在日志里只出现一次,Redis 里的 lock 键在任务结束后被释放,worker 重启也不再触发重投,重复率降为零。
补一句。Redis 做 broker 时消息的可见性靠超时兜底,任务比超时还长就一定会被别的 worker 再拿到,幂等锁比调参数更可靠。这类问题最好一次改到位,反复小改反而容易把原因搅混。
