一家做母婴用品的客户搞了个限时促销,开场五分钟客服就炸了:下单成功的订单比库存多出三百件。紧急下架、挨个道歉、赔违约金,一场促销变成一场危机。问题出在哪?查库存和扣库存是两步操作,平时人少没事,高并发下几百个请求同时读到还剩十件,人人都以为自己抢到了。这是后端系统开发最经典的并发考题,答案其实有一套递进方案。
最简单的一招就很有效
把两步并成一步:不用先查再扣,直接一条更新语句解决——扣减库存时带上条件,库存大于等于购买数量才允许更新。这条语句在数据库里是原子操作,一百个请求并发进来,数据库排队执行,谁也不多扣。多数中小系统的秒杀场景,光这一招就够用,根本不需要什么高深架构。很多人低估了数据库单行的处理能力,一台普通服务器每秒处理几百上千次这样的更新不成问题。
并发再往上走怎么办
- 乐观锁:表里加个版本号字段,更新时核对版本,改过的请求重新读取再试,适合冲突没激烈到秒杀级别的场景;
- Redis预扣:真有大促量级的,把库存提前放进Redis,请求先在Redis里原子扣减,扣成功的再异步落库,数据库只承担涓涓细流;
- 限流排队:请求先在入口排队,超出库存量的直接告知已抢完,让后面的流量根本不碰库存逻辑。
方案从上到下成本递增,按真实并发量选,别为一个日订单两百的店上Redis集群,杀鸡用牛刀的钱不值得花。
还有个常被漏掉的尾巴:下单成功不等于成交。用户拍了不付款的占相当比例,库存被占着,别人买不到。要配支付超时释放:三十分钟未付款自动取消订单、库存回滚。释放逻辑也要防并发——取消订单和用户恰好完成支付撞车的极端情况,用订单状态做原子判断,先到先得。超卖防住了,少卖同样要防,两头都是钱。
那位母婴客户的后续处理是个正面样本:修复用了原子扣减加超时释放,下一次促销前主动做了一轮并发测试,模拟五百人同时抢十件库存,实测成交正好十件。预案做在活动前,才叫预案。
超卖的背面是少卖
防住超卖只是及格线,库存管理还有另一头:付款超时及时释放,取消订单准确回滚,活动结束后对一遍账。后端系统开发里库存模块的完整度,往往在一次活动里就见分晓,平时看不出差别,高峰当天全是分水岭。
两条建议:有秒杀、限量、拼团这类玩法的,立项时就把并发场景告诉技术方,让方案里写明扣减方式;每次大促前做一轮真实的并发模拟测试,比格网络帮客户做大促护航时这叫抢购演练,演练通过的促销才敢开闸,活动的口碑经不起超卖一次。