问题出现得很突然,日志倒是留得挺全。Celery 任务开了 acks_late,上游超时后客户端又手动重试,队列里同一笔订单出现五条消息,消费端把发货流程跑了五遍。

刚开始盯着「visibility timeout 小于任务耗时,消息被提前重新入队」,折腾了半个多小时,排掉了。

排查方向其实有限,先把可能性排一排。业务重试与 broker 重投叠加,同一条消息被多次投递。少数情况下是任务没有幂等键,消费端无法判断是否处理过。

不容易想到的是,消费端异常后整批 nack,同批其他消息一起重投。

上来先按业务键统计重复消息数量

python -c "import collections;print(collections.Counter(['A1','A1','A2','A1','A1']))"

随后用订单号做幂等键,处理前先占位

redis-cli SETNX order:lock:A1 1

接着把幂等记录写成带过期的状态,避免永久锁死

redis-cli SET order:done:A1 1 EX 86400

调整可见性超时到任务耗时的两倍以上

python -c "print({'visibility_timeout':1800,'avg_task_seconds':240})"

改完先别撤,确认这几项。重放同一批消息五次,实际只产生一条发货记录,重复消息在幂等层被直接跳过。

补一句。重试链路里幂等是唯一可靠的挡板,去重键要选业务主键而不是消息 id,因为重投时消息 id 通常会变。报错文案容易误导,真正的原因往往在相邻那一层的日志里。

作者 9823189