codingXiaxw/seckill

有几个值得思考的问题

Opened this issue · 5 comments

1.如何解决超卖的问题?场景描述:若某件商品库存只有5件,如何保证第六个客户不会秒杀到第6件该商品。

2.后端采用Redis作为中间层缓存seckill对象,seckill对象包括的字段有ID、时间、库存量。那么问题来了,当Redis内部缓存的Seckill对象库存量发生变化时,此时Redis如何将这种变化的状态(即库存量的减少)缓存到自身中?

希望大家在利用这两个项目做练习项目时,带着这两个问题进行思考,知道解决方案的人可以在底下留下自己的思路。

另外大家有啥疑惑的,也可以在这个issue下提出来,例如"假如10个人同时点击秒杀,要怎么处理啊"之类的,畅所欲言吧

我这里说说自己的想法吧,对于第一个问题,我是这样设计保证不会发生超卖情况的。当执行秒杀操作的时候,数据库进行的是两个操作:1.减库存update。2.增加用户购买明细insert。所以这里会使用到mysql的事务机制,我在设计表的时候指定mysql的引擎engine = INNODB,所以mysql会采用行级锁的机制来操作事务机制。所以当用户1得到行级锁在执行update操作的时候,用户2必须要等到用户1执行完upadte、insert操作并释放掉行级锁后才能得到这个行级锁并进行秒杀操作。此时若用户1将最后一件商品秒杀掉后,用户2再去秒杀的时候会出现秒杀完毕的异常,我们采用的解决方法是捕获这个秒杀完毕的异常并以用户的角度向他表明。所以我觉得不会发生超卖的情况。

xwlcn commented

限时抢购就不应该用数据库来直接操作。一般是抢购前将所有待抢购的商品全部读取到 redis 缓存中,并且不是存序列化对象,序列化只会浪费时间,而抢购商品唯一有用的属性就是库存量,再就是商品 id 也是必须的。所有下单请求全部直接操作 redis,由于 redis 天生是单线程的,所以不可能出现超售,但要注意的就是可能会出现少售的情况,比如A用户秒杀订单中包含3个商品a(1个),b(3个),c(1个),此时a,b,c库存分别为99999,2,99999,如果不做处理的话那么操作redis时是依次对a,b,c进行库存判断,而这个判断方法一般不是先取出来再和订单中的商品数据进行比较,而是直接通过原子操作减掉库存并返回库存,然后判断库存是否小于0,最后再决定是否回滚,此时整个下单就不是原子的了。前面的A用户遍历到b商品时将库存减到了-1,所以下单失败,需要进行回滚,若此时出现B用户下单,并且订单中有b商品2个,正好在A用户回滚前进行b商品库存判断,结果发现b商品库存为-1-2=-3,很明显A用户下单失败,b商品库存理应为2,也就是说B用户可以下单成功的,却出现了两个都下单失败的情况,也就是出现了少售。解决办法,可以利用redis执行lua脚本是原子操作的特性,将下单库存操作放到lua脚本里,回滚也是在脚本里,这样一来就解决了少售问题。

@xwlcn

  1. 你提到了商品全部读取到 redis中,但是不是序列化对象。那么这些商品在 redis中是如何存在的?【不序列化放不进去吧?】
  2. 关于少售问题,a用户在回滚的过程中,b 用户可以下单购买吗?我的理解是:下单减少库存是1次操作,回滚库存是一次操作。这2次操作可能分别由线程操作,中间可能有其他线程插进来,所以不具有原子性?
  3. 提到的 lua脚本,其逻辑是 减库存后直接判定是否小于0,如果小于再回滚,这是一次原子操作?

我这里说说自己的想法吧,对于第一个问题,我是这样设计保证不会发生超卖情况的。当执行秒杀操作的时候,数据库进行的是两个操作:1.减库存update。2.增加用户购买明细insert。所以这里会使用到mysql的事务机制,我在设计表的时候指定mysql的引擎engine = INNODB,所以mysql会采用行级锁的机制来操作事务机制。所以当用户1得到行级锁在执行update操作的时候,用户2必须要等到用户1执行完upadte、insert操作并释放掉行级锁后才能得到这个行级锁并进行秒杀操作。此时若用户1将最后一件商品秒杀掉后,用户2再去秒杀的时候会出现秒杀完毕的异常,我们采用的解决方法是捕获这个秒杀完毕的异常并以用户的角度向他表明。所以我觉得不会发生超卖的情况。

@codingXiaxw
老哥,这不是你设计的吧?