欢迎光临
我们一直在努力

90%开发者踩过的坑:高并发下的超卖问题!

高并发的电商等特殊的场景下有一个不容忽视的问题——超卖问题

这个问题是怎么产生的呢?

我们举一个简单的例子。

在前端点击比如说抢购的按钮后,开始发起请求。

我们往往会先判断比如说我们商品的库存、看看我们的库存是否够呢?

比如说现在的库存是1。我们有两个线程

分别是线程1和线程2。

在理想的状态下,当线程1发起请求时,我们开始

查询库存。当发现库存为1时,好的,我们可以直接削减库存。然后线程2开始请求,它发现库存已经变为零了。那么就给前端返回一个比如说:商品库存不足。

但是现实情况是,这两个线程可能在大概率下并不是线性执行,而是并行执行,也就是说,这个查询库存,并且削减库存的数据库操作可能是交叉执行的。

当线程1开始查询库存时,库存还剩下1个。那么它开始准备削减库存,但是在它还没有削减完成时,线程2进来了,它也发现还剩下最后一个,好的它也开始准备削减库存。

那么在这种情况下,就产生了超卖问题。

也就是说最后的结果就是:只有1件货,那么你卖了2件。

那么这种超卖问题的解决方案有哪些呢?

超卖问题作为多线程高并发场景下的常见问题,我们往往选择使用加锁的方案来解决线程穿插的问题

既然我们决定加锁来解决这种超卖问题

锁主要分为悲观锁乐观锁两种

在这种场景下,我们该选择哪种锁呢?

顺便提一下:乐观锁和悲观锁并不是真正有这种锁、他们只是一种锁的设计理念

悲观锁:认为线程安全一定会发生、因此在操作数据之前先获取锁,确保各个线程在串行而非并行的情况下对数据进行操作、例如Synchronized。这也就导致了性能相对来说其实并不是很好,特别是在高并发的情况下,每个线程只能一个一个执行。
乐观锁:认为线程安全其实并不一定会发生,所以更多像一个乐子人,因此并不加锁,只是在修改数据时判断有没有其他线程对数据进行了修改。
如果没有被修改则认为是安全的,自己才会更新数据。
如果已经被其他线程修改了数据,那么说明发生了线程安全问题,会提示进行重试的操作。

乐观锁的关键:怎么判断有没有其他线程修改了数据?

常见的有两种方法

版本号法:给数据增加一个版本号的字段:每当数据发生了修改,那么版本号就会加1

        当我想要判断这个数据有没有被修改过时,那么只需要判断版本号有没有变化即可。

        不过其实我们可以思考一下、我们是不是可以使用库存来代替版本号的这个字段呢、在最后需要进行库存削减时,我们判断一下,我们查到的库存和数据库中的库存是否一致呢?

这个就是CAS

但是其实乐观锁也是有一定的弊端的,那就是在修改数据时过于小心,这一点可能会导致大量的线程执行失败,所以我们需要进行一些改进。

还是那个业务场景、现在有一百个线程,他们在同一时间进行抢购,我们不再选择在修改数据时,对查到的库存和数据库中的库存进行对比,而是选择只要库存大于零即可,那么就可以修改数据。

// 选择与查到的库存进行对比
set stock = stock – 1 where id = ? and stock = ?
// 选择只要库存大于零即可
set stock = stock – 1 where id = ? and stock > 0

虽然我们通过 set stock = stock – 1 where id = ? and stock > 0 解决了超卖问题,但这依然是基于数据库层面的解决方案。

在真正的“秒杀”场景下,并发量可能高达几十万QPS,直接让数据库扛这几十次更新请求依然会造成巨大的压力,甚至拖垮数据库。

因此,实战中我们通常会结合 Redis 来做预扣减库存,利用内存的极高性能抵挡绝大部分流量,只有抢到Redis锁的请求才有资格去更新数据库。

另外,如果系统是集群部署,Synchronized 这种单机锁就失效了,这时候就需要请出 分布式锁 了。

赞(0)
未经允许不得转载:171主机测评 » 90%开发者踩过的坑:高并发下的超卖问题!
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址