事情本身不复杂,线索都在日志里。支付回调处理里用订单号做唯一索引防重,重复回调插入触发 1062,代码没捕获直接把 500 返回给支付方,对方按退避策略持续重试,QPS 越滚越高。
第一反应是「重试没有去重窗口,短时间内同一订单反复进来」,验证下来不成立。
把日志和配置对完,能解释这个现象的基本就这几处。重复键异常被当成系统错误抛到上层。还有一种是返回非 2xx 让支付方认为未成功继续重发。
不容易想到的是,唯一索引只建在自增主键上,业务订单号没有约束。
从日志确认异常类型是重复键
grep -c 'Duplicate entry' app.log
捕获重复键后直接返回成功响应
python -c "import pymysql;print(pymysql.err.IntegrityError)"
给业务订单号补唯一索引
psql -d pay -c 'create unique index concurrently uk_pay_order on payments(order_no)'
等前面都确认过,再用同一回调连发 20 次观察响应码
for i in $(seq 20); do curl -s -o /dev/null -w '%{http_code}\n' -X POST http://app/pay/cb -d @cb.json; done
怎么算真的好了,看的是同一批现象。重复回调全部返回 200 且不再新增记录,日志里重复键被记录为已处理,重试流量在几分钟内回落。
容易忽略的是,幂等场景里重复是正常路径不是错误,返回 5xx 会把对端重试机制变成放大器,重复键要捕获并当成功处理。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。
