虚拟商品最容易亏钱的事故不是发慢了,是同一张卡发给了两个买家。
第二个买家用不了,来找你;你补一张给他,成本翻倍;如果他先申请了退款,货和钱就都没了。
这篇讲清楚这件事是怎么发生的,以及怎么防。
事故是怎么发生的
假设库存里有 100 张卡,两个买家在同一秒下单。系统要做的是"取一张没用过的卡"。
如果取卡逻辑是这样的:
- 查询:找出第一张状态为"未使用"的卡
- 发送:把它发给买家
- 更新:把这张卡标记为"已使用"
那么在第 1 步和第 3 步之间,存在一个时间窗口。两个请求如果都落在这个窗口里, 就会查到同一张卡,然后各自发出去。
单量小的时候可能几个月都不出事;一次活动、一波流量,就集中爆发。
三个概念分别解决什么
这三个词经常被混着说,但解决的不是同一个问题:
- 先进先出:解决的是顺序问题——先入库的卡先发出去,避免旧卡长期积压过期。它不防重复。
- 库存计数:解决的是数量问题——还剩多少张,什么时候该补货。它也不防重复。
- 原子锁:解决的才是重复问题——把"查询 + 标记"合并成一个不可分割的动作, 一张卡被某个请求取走的瞬间就已经锁定,另一个请求根本查不到它。
所以判断一个工具防不防重发,要问的是第三个,不是前两个。
除了取卡,还有一处会重复
即使取卡不重复,补发也可能造成重复:订单已经发过一次,因为某种原因又触发了一次发货。
合格的做法是补发时先检查这笔订单的发货记录,已经成功发出的不再重复取卡, 而是重新发送原来那张卡的内容。这样买家不会收到两张不同的卡,你也不会白白消耗库存。
常见问题
同一张卡会不会发给两个买家?
在有原子锁的实现下不会。取卡时库存计数与锁定是一个原子动作,一张卡被取走即刻锁定, 并发请求不可能取到同一张。
库存不够时会怎样?
订单会被暂存,同时按你配置的渠道推送告警,补货后自动继续发出,不需要手动补发。 建议同时设置低库存预警阈值,在耗尽之前就提醒。
导入卡密时重复了怎么办?
导入时应当自动去重,并且是跨批次去重——只在单次导入内去重的话, 同一批卡分两次导入照样会重复入库。
已经发出去的卡还能回收吗?
发出去的内容无法收回。所以真正的防线在发之前,不在发之后。
这件事的成本是不对称的:防护做在前面只是一次配置,出事之后是持续赔付。