一、 秒杀抢购场景下防止商品超卖的技术实现思路
1. 通常解法
方案一:
在购买下单前先查询数据库库存是否大于0进行判断,有库存在进行减库存下单操作,反之下单失败。
具体做法:用户点击活动页面时,后台进行查询数据库当中的number(库存)字段。判断number库存是否大于0,如若小于0,则给用户返回库存不足抢购失败。如若大于0,则进行下单操作,生成订单号,然后进行修改库存(number-1),判断执行操作是否成功,如若成功给用户返回抢购成功,如若失败则给用户返回抢购失败。
问 题: 当并发量大的时,数据库number字段会出现负数,所以会出现超卖问题。
方案二:
1)将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,如果继续减库存是将会返回false
2) 开启事务锁住操作的行
具体做法:
用户操作->开启事务->查询数据
->判断number是否大于0
->如若失败返回库存不足,进行事务回滚
->如若成功,进行下单操作
->修改库存number-1,进行判断
->如若更改库存失败,返回抢购失败
->如若执行成功,进行事务提交,返回抢购成功
问 题: 事务开启时,会产生一个独占锁,当并发量高的时候,就会造成有些SQL请求阻塞,从而导致并发用户出现请求超时或页面无法响应的情况,没有返回数据,只是一个空白页,所以造成用户体验不好,同时也会造成DB服务器压力非常大。
2. 通用大牛级解法
思路:通过消息队列来处理,起到高并发场景下的异步,消峰作用。
具体做法:在活动开始之前,将我们活动的商品放到我们的队列当中,开始后我们需要一个排队队列和抢购结果队列。高并发情况,先将用户插入排队队列,用一个线程循环处理从排队队列顺序取出每一个用户,判断用户是否已在抢购结果队列,如果在,则已抢购,否则未抢购,开始减库存,生成订单,支付结算等操作,同时将订单结果数据同步到MySQL。
3. 解法对比及优缺点:
常见解法,
方案一:在高并发情况下会出现库存为负数也就是超卖的问题,此方案不可取。方案二:确实解决了商品超卖的问题,但是在高并发情况下还是会给DB造成死锁和严重阻塞的情况,且用户体验比较差。
通用大牛解法:这里消息队列可以采用的消息中间件,像RabbitMQ,Kafka,ZeroMQ等等,我们样例演示采用的是redis的list数据类型实现队列,因为redis的pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用,而采用 mysql事务在高并发下性能下降很厉害,文件锁的方式虽然是非阻塞形式,但是用户体验很差。
通过对比,通过消息队列的方式可以对抢购秒送这种高并发场景下进行消峰,有效提高了系统的高可用和高性能, 大大减低数据库的读写压力,同时也提升了用户体验。
4. 延伸及扩展问题回答参考
问题:通过消息队列还有那些常见的应用场景?
解答:
1.在线竞拍,抢购等瞬间多用户高并发的场景都可以通过消息队列来消峰处理
2.在用户生成订单时也会有很多关联操作需要处理,比如:计算用户积分,计算分销商的佣金,给用户发送提醒通知,等等业务逻辑,这些是不需要在下订单那一刻马上完成执行的,所以这种情况下就可以通过消息队列的的形式,将这些非必需的业务进行解耦,异步处理,从而降低订单业务逻辑的拆分解耦,降低单个业务的复杂度。
5. 项目中体现经验的点
项目开发过程中,充分理解消息队列的应用场景,消峰,异步处理和业务解耦。在高并发的场景下可以考虑通过消息队列的进行消峰异步处理,从而适当有效降低服务器的压力,同时也可以对复杂的业务进行拆分解耦,实现了高并发情况下高性能和高可用。
6. 参考资料
RabbitMQ官方文档: https://www.rabbitmq.com/documentation.html
Redis官方文档 : https://redis.io/documentation