压测时一个用户 ID 在 200 毫秒内提交两次,库里出现两笔 order_no 不同的待付款订单,库存扣了两次。前端按钮的 disabled 状态在第二次点击时还没生效。日志里能对上时间点,这种反而好排。

接口只在前端做了防重复,服务端没有任何幂等键,两次请求被当成两笔独立业务。请求间隔小于库存扣减的耗时,行锁也没挡住。这一点先确认清楚,后面就不用反复试。

$ok = $redis->set("seckill:{$uid}:{$aid}", $reqId, ['nx', 'ex' => 300]);
if (!$ok) { echo json_encode(['order_no' => $existNo]); exit; }

「秒杀接口幂等」确认了原因之后,改动尽量只碰那一处,顺手改的东西后面最容易出问题。线上出问题先对时间点,跟发布、备份、任务执行的时刻排一排,能省掉一半功夫。

处理上按这个顺序:前端生成一个 request_id 随请求带上,服务端用 SETNX 占位,五分钟内同键只处理一次;数据库层给 user_id 加 activity_id 的唯一索引,作为最后一道防线;重复请求直接返回第一次的订单号,让用户看到同一个结果。

结果对不对很好判断,连点脚本跑一百次,同一用户只有一笔订单,重复请求返回同一订单号。

作者 9823189