9 月 16 号上午,客服转来三条几乎一样的订单,同一个用户、同一件商品、时间差不到 2 秒。金额一样,收货地址一样,只有订单号不同。
为什么会三次
前端设了 3 秒超时,网络稍慢没等到响应就自动重试,最多重试 2 次。后端下单接口没有任何去重,收到一次插一条。三次全成功了。
这跟之前积分那次不一样:那次是用户手动点,这次是程序自己点的,人根本不知道。
幂等键放在哪
讨论过用用户 id 加商品 id 加时间戳,但时间戳一变就不算重复,等于没防。最后用客户端生成的 request_id:下单页一加载就生成一个 uuid,跟着这次提交一起发,重试时还是同一个。
服务端拿 request_id 在订单表上建唯一索引。重复进来插不进去,捕获冲突后不报错,把已有那条订单查出来原样返回。前端拿到的是同一个订单号,不会以为失败再试。
关键还是那句:重复请求返回原结果,别返回错误。
顺手改的两处
- 前端超时从 3 秒调到 8 秒。多数「超时」其实只是慢,不是没到。调完之后重试次数肉眼可见地少了。
- 重试只针对明确的网络错误,5xx 和业务错误不重试。第一版没分,业务上提示「库存不足」也重试了两次,白耗。
补数据
已经生成的 3 笔,留 1 笔,另外 2 笔走取消流程,不硬删,取消记录里写明「重复提交合并」。支付那边只扣了 1 笔,没造成重复扣款,这是不幸中的万幸。
导出一张明细给客服,一共 7 列,让他们照着跟用户解释。
后来
把「所有创建类接口」过了一遍,一共 9 个,逐个看有没有幂等键。有 4 个当时没出过问题,也补了。评审单里加了一行:这个接口被调两次会怎样。
那三张订单截图没删,放在内部文档的「重复」那一节。跟《两小时》挨着。

