企业级高并发下,商城秒杀系统设计之库存超发的解决方案

在企业中,特别是商城系统,秒杀是常见的应用,我们经常会遇到这样的场景,比如:秒杀活动中,有10万人抢购,如果我们只卖100件下面这个婴儿纸尿裤,那该怎么办法,这么多直接打DB上,肯定直接挂了,如果能保证库存不超发呢?

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

商品秒杀

一般情况下,在企业应用开发过程中,执行抢购的逻辑流程如下:

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

抢购流程

但凡是个秒杀,都怕超卖,秒杀的特点就是这样时间极短、 瞬间用户量大,

假如100纸尿裤,现在还剩下最后2个,第一个购买请求来了,买1个,从数据库中读取到库存有2个,数量够,可以买,减库存后,更新库存为1个。

接下来第二个购买来了,想买2个,发现库存为1,不够就不可以买了。

这样是没问题的,但在在商城开发过程中,特别是大流量高并发情况下,这2个购买请求很可能是一起来的,他们都读到库存是2,都可以买,都去减库存,结果库存变成 -1了,此时就出现超发了。

还辛亏不值什么钱,如果是iPhoneX MAX,没有处理好,100个结果给卖出了200件,那估计就亏大发了。而你作为开发的程序员,也只能祭天了。

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

祭天

超发的解决方案:

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

第一种悲观锁

悲观锁主要是查询时添加更新锁,如果一个线程读库存时就将数据锁定,不允许别的线程进行读写操作,直到库存修改完成才释放锁,那么就不会出现超发问题了。

<code>// 这里db事务开始 

select id, product_name, stock, ...
from goods
where id={id} for update

update goods
set stock = stock - {quantity}
where id = {id}
// 事务db结束/<code>

这种方式的优点:

代码实现,思路都简单,主要从数据库层面解决了超发问题。

缺点为:独占锁对数据库性能影响较大。

第二种乐观锁

主要实现是通过版本号方式实现。程序主要是先获取数据库的当前版本号,执行完业务流程进行库存更新时,判断当前线程持有的版本号是否与数据库中的版本号相同,如一致则更新库存并增加版本号。

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

示意图

演示代码:

<code>update goods set stock = stock - {num}, ver = ver +1    where id = {id} and ver = {version}/<code>

他的优点呢,性能相对于悲观锁好一些,没什么阻塞,但是缺点就是要可能添加重试几次的逻辑。

第三种Redis(+lua)机制实现

我们知道,使用数据库的方式和使用内存的读写速度是不在一个量级别上的,如果使用Redis,那速度肯定快很多的。

我们把第一种方案的悲观锁的数据库换为redis,把查询库存的操作与更新库存的操作绑定在一起,不被其他线程影响。

我以lua脚本为例,lua脚本读写,redis执行lua,也可以保证原子一致性,lua脚本逻辑:

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

lua脚本逻辑

具体示例代码:

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

lua示例代码

我们可以用户购买请求的时候,调用lua处理。上面的处理流程中有一步”把购买记录写入 redis“,我们还是需要把数据同步到数据库中,可以使用一个定时程序,把 redis 中的记录写入数据库。或者,我们可以把购买记录写入消息队列,然后通过消费者同步到数据库。

Redis方式的优点:

性能最优,读写速度非常快,实现简单。

Redis方式的缺点:

Redis方式,需要处理数据库的同步问题,如果并发量再大,还需要保证 redis 本身是高可用的,更多的时候需要,做redis集群等。

总结:

其实,秒杀在开发过程中,要做充分的流量预估。比如使用的Redis,单机的Redis我感觉3-4W的QPS还是能顶得住的,如果更大量的请求进来,我们需要考虑的点就很多了,缓存雪崩缓存击穿缓存穿透等,Redis集群,主从同步等。限流方面,包括前端限流后端限流。等等,太多细节要考虑了。

最后附上一个不错的大神搞的总结图,还不错,值得收藏,整个思路就是如下,更多流程细节,尽在图中啦。

企业级高并发下,商城秒杀系统设计之库存超发的解决方案

构架图

高并发秒杀实际要做的远我们说的要多,后续会继续分享。觉得不错,记得请多多转发,关注哦。后续迷神会继续分享更多的精彩内容哦。


分享到:


相關文章: