支付回调做了幂等表,按 TTL 24 小时清理,第三天财务发现一笔退款被执行了两次,时间差 39 小时,回调方声明重试窗口是 72 小时,幂等记录早已被清掉。

一开始怀疑是「幂等键由回调方生成,两次重试用了不同的键」,查了一圈,方向不对。

原因不止一个,得分开看。幂等记录的保留时间短于上游的重试窗口。还有一种是清理任务按插入时间直接删,没有考虑未完成的业务状态。

不容易想到的是,幂等判断和业务写入不在同一个事务里。

第一件事是核对上游声明的重试窗口

python -c "print({'provider_retry_window_hours': 72, 'local_ttl_hours': 24})"

随后把幂等表保留时间调到窗口的两倍

psql -d pay -c "ALTER TABLE idem_record ALTER COLUMN expire_at SET DEFAULT now() + interval '7 days';"

接着清理时只删已完成且过了窗口的记录

psql -d pay -c "DELETE FROM idem_record WHERE status='done' AND expire_at < now();"

幂等判断与业务写入放进同一事务

psql -d pay -c "BEGIN; INSERT INTO idem_record(key) VALUES('cb-8821') ON CONFLICT DO NOTHING; COMMIT;"

别只看报错消失,验证要多做一步。跨三天重放的同一回调被判定为重复,退款只执行一次,幂等表保留量与窗口匹配,清理任务不再删掉仍在重试期内的记录。

容易忽略的是,幂等记录的寿命要覆盖对端最长重试窗口,少存一天就可能多付一笔,宁可多留几天也别卡着业务当天算。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。

作者 9823189