这类问题我又碰上了一次。订单同步任务设了 autoretry_for 和 max_retries 3,接口偶发超时后 Celery 重试,数据库里出现两条 uuid 不同但内容相同的记录,下游统计直接翻倍。

第一反应是「任务在超时后才完成,重试与首次执行都成功」,验证下来不成立。

原因不止一个,得分开看。重试时任务参数里带着新生成的 uuid;写库用的是 insert 而不是 upsert;worker 的 acks_late 开着,消息被重复投递。

动手时先确认幂等键从哪里来

python -c "print({'task_id': 'fixed-by-order-no', 'uuid': 'regenerate-each-retry'})"

再往下把写库改成按业务键 upsert

python -c "print('INSERT ... ON DUPLICATE KEY UPDATE updated_at=VALUES(updated_at)')"

接着用任务 id 做去重表拦截

redis-cli setnx task:lock:order_20261114 1

剩下的就是关闭 acks_late 或缩短可见性超时

python -c "print({'task_acks_late':False,'visibility_timeout':3600})"

怎么算真的好了,看的是同一批现象。同一订单反复触发任务后库里只有一条记录,重试计数正常,下游统计与源系统一致。

容易忽略的是,自动重试不是幂等,超时类失败尤其危险,业务键必须由上游传进来而不是任务内部生成。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189