欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 培训 > Redis分布式锁在高并发环境下的超卖问题

Redis分布式锁在高并发环境下的超卖问题

2024/10/24 4:40:42 来源:https://blog.csdn.net/weixin_44390790/article/details/140365087  浏览:    关键词:Redis分布式锁在高并发环境下的超卖问题

先看这样一段代码,购买商品,扣减库存的逻辑代码

当用户下单,并且调用扣减库存的接口时,先判断商品库存是否还有,因为是秒杀场景下,太多请求都打到数据库,可能会导致数据库崩溃,所以会使用redis缓存,保存商品库存数。

由于是微服务架构,为了实现高并发,我们的商品业务代码(包含库存这部分的代码)不可能只部署在一台服务器上,肯定会在多台服务器上都运行。

场景:

假设:当多个请求同时执行到从redis缓存中取库存值的时候,stock=100,判断库存>0成立,然后进行下面的逻辑,当扣减1个库存时,都会把stock=99写回redis缓存的时候,这时,明明卖了2个,但是redis缓存中的数据stock却等于99,就超卖了。

改进:

我们使用同步代码块,使用Java的synchronized锁,只能解决商品业务代码部署在一台部署上的场景,因为synchronized锁是JVM进程级别的锁,对于多台服务器部署的场景,每台服务器都可以获得它的进程的synchronized锁,还是不能解决微服务架构下的超卖问题。

继续改进:

我们使用redis提供的原生命令setnx实现分布式锁(Java客户端jedis提供的方法setIfAbsent和原生命令setnx是一样的),这个锁加在了redis服务器上,一次只有一个请求能获得,当代码执行完成后,释放锁。好像解决了超卖问题,但是如果代码执行到中间时,代码抛出异常了,锁就一直存在在redis中,其他服务永远无法进入redis,就死锁了。

继续改进:

使用try...finally,就算代码中间抛了异常,也能将锁释放掉。

但是当服务器执行到中间,服务器挂掉了,锁还是不会释放,怎么办?

继续改进:

一般会给锁设置一个超时时间,就算服务器挂掉了,redis服务器中的锁还是会自动释放。

但是,当服务器获取了锁之后,正准备设置超时呢,由于是两条命令分开执行,还是可能会突然服务器挂了,是有可能没设置成功超时时间的,这把锁还是永远在redis中

所以一般使用一条命令,设置锁的同时设置超时时间,发送到redis服务器。

继续改进:

这样好像就没啥问题了,但是在高并发场景下,接口的响应可能会变慢。

当一个请求获取到锁时,处理业务时间由于超过了10s,还没有完成扣减库存,锁过了10s,自动释放了,由于高并发场景下,同时有源源不断的请求过来,其他请求就获得了锁,这时,又会导致超卖问题。这时,可能不只是超卖一个两个的问题,第二个请求加锁成功后,如果第一个请求完成了,执行 解锁 操作,会把第二个请求加的锁给释放掉了,其实这时第二个请求还没执行完成,然后请求三又会获得锁,造成一系列的超卖问题。

造成这个问题的原因,就是线程1把线程2的锁给释放掉了,线程2把线程3的锁给释放掉了,锁一直失效。

继续改进:

中小公司,当并发量不是特别大的时候,是没问题的。

但是这段代码仍然存在线程1加的锁,当线程1没执行完成,锁自动超时释放的情况。在高并发场景下,其他请求又可以获得锁,进入扣减库存操作了,一旦重复扣减同一个数字,又造成超卖问题了。

解决方案:

当线程1获得锁之后(假设设置超时时间t = 30s),在当前线程1的代码内部会开辟一个异步线程2,每过10s (1/3*t) 就会检查线程1是否执行完毕,当线程1还没执行完毕,异步线程2就会重新把这个锁的超时时间设置成30s,这个机制叫watchDog。

异步线程2为什么能获得这个锁的使用权?因为redis的分布式锁是可重入锁,线程2在线程1内部,能直接获得redis中的这个锁。



这个watchDog的机制自己实现起来比较复杂,所以推荐使用Redisson分布式锁。

Redisson分布式锁的实现机制就是基于以上这些逻辑。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com