第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例

背景

现在面试,一般都会聊聊分布式系统这块的东西。通常面试官都会从服务框架(Spring Cloud、Dubbo)聊起,一路聊到分布式事务、分布式锁、ZooKeeper等知识。本文以扣减库存为例,慢慢的阐述如何使用Redis实现分布式锁。

一、从简单例子程序说起

如下代码就是一个简单的扣减库存的例子,为了简单起见,我将库存放在redis中,初始设置库存的值是50,方便测试。
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
代码如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
测试方案:
1、单线程测试,在浏览器直接访问 http://localhost:50000/itemcenter/decrease/stock
日志如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
我们去redis中,去查看的结果如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
2、利用JMeter模拟高并发的场景,扣减库存,瞬间100个线程,请求2次。
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
并发请求扣减库存的接口:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
从日志看来,在高并发的情况下,程序明显存在问题的:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例

二、加synchronized是否可以解决问题

如下代码所示,我们加了synchronized锁定扣减库存的逻辑。现在我们重新设置redis里面存储的库存仍然是50个。
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
测试方案:
1、单线程测试,在浏览器直接访问 http://localhost:50000/itemcenter/decrease/stock
日志如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
2、利用JMeter模拟高并发的场景,扣减库存,瞬间100个线程,请求2次。
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
并发请求扣减库存的接口:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
从日志看来,在高并发的情况下,程序没有出现什么问题:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
但是这种方案还是存在问题的:
1、我们知道synchronized是单机的,如果存在多个JVM实例,那么synchronized也是没有办法锁住的。

我们现在演示下,在多JVM实例下,synchronized也是没有办法锁住的的。

我们启动两个应用实例,分别启动在50000,50010两个端口,并且使用Nginx做负载均衡:

第一步: Nginx的配置
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
第二步: 并且将库存的值初始设置为50,代码如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
我们已经启动了两个实例如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
第三步: 利用JMeter模拟高并发的场景,扣减库存,瞬间100个线程,请求2次。
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
并发请求扣减库存的接口:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
从日志看来,在高并发的情况下,程序明显存在问题的:
我们看到在50000端口的应用日志:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
我们看到在50010端口的应用日志:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
从以上的情况,我们可以知道, 如果存在多个JVM实例,那么synchronized也是没有办法锁住的。

三、自己动手利用Redis实现分布式锁

第一步: 代码如下:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
第二步: 我们已经启动了两个实例,并且利用JMeter进行压测,通过日志发现,我们知道Redis实现的分布式锁已经生效了。

存在的问题
问题一: 如果程序刚执行完第21行, 该程序就挂了,那么就会造成死锁。
问题二:如果程序执行完了第21行,但还没有执行第33行,中间就发生了异常或者程序挂了,那么也会造成死锁。
我们尝试改进的方案:
第二十一天:浪迹天涯网上商城(1.0版本)--浪迹天涯商城Redis高并发分布式锁实战---以扣减库存为例
我们把释放锁的代码放在finally的代码块里,可以一定程度上解决异常带来的死锁问题。
还是我们还是不能解决程序执行完这段代码的时候:
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, “lockValue”);
程序突然挂了,还是会产生死锁的。