目录
1、如果上锁之后,JVM宕机,那么锁就会一直存在Redis中,造成死锁
一、分布式锁的业务应用场景
1.1、用户下单业务流程图
1.2、超卖原因分析 
1、多个用户同时对商品A进行下单
2、多个请求此时分别打到了多个订单服务中
3、多个订单服务对多个库存服务进行查询库存
4、此时多个库存服务同时查询数据库中的库存都为1
5、同时多个库存服务返回库存足够并扣减库存
6、所有用户下单成功,库存为-1,出现了超卖
二、Mysql解决分布式锁
2.1 解决方案
Mysql最常用的就是表(废话)。使用Myql可以根据商品的主键等唯一性的值作为标识,新建一个锁表
2.2 性能分析
以下图表数据是阿里云提供的对 MYSQL8.0 对阿里云的测试结果
基于MYSQL实现分布式锁,以下的几点我们需要权衡下
1、MYSQL的压测结果中,虽然取得不错的成绩;
但是在千万级并发的情况下使用MYSQL作为分布式锁的解决方案的情况下
硬件的设备需要有一定的要求支持,对企业的成本过高。
2、MYSQL是基于磁盘IO;
而分布式锁的应用场景是并发量较高的情况下发生,基于磁盘IO,我们更希望的是使用内存IO
经过上面的分析,我们希望锁的实现是基于内存,而且要快。那么我们第一时间会想到什么?
没错,那就是Redis!
三、Redis实现分布式锁
Redis是一个开源,内存存储的数据结构服务器;
可用作数据库,高速缓存和消息代理队列。具体学习可阅读以下文章:
Redis从零到进阶知识总结基于Redis中Setnx命令实现,如果上锁成功,则扣减。
如果上锁失败,则上锁失败;返回下单失败
3.1 解决方案带来的问题
1、如果上锁之后,JVM宕机,那么锁就会一直存在Redis中,造成死锁
我们可以根据Setnx (setnx key ttl value)的命令参数设置一个过期时间,防止JVM宕机后造成的死锁问题
2、基于问题1解决之后,会产生以下问题
如果在上锁成功之后,并且设置了一个过期时间。此时服务A在执行业务逻辑中可能由于IO、网络的不稳定性、JVM突然的STW导致的时间超过了过期时间之后,继续执行业务逻辑的同时,另外的一个集群服务B进行上锁(此时Redis中的锁已过期,此时服务B进行上锁之后,服务A这个时候才执行文对应的业务逻辑,并且把锁删掉,会把服务B的锁删除!)
防止删除别人锁问题:
我们可以根据Key Value 键值对的Value值,每个服务每次上所产生一个唯一的标识
当释放锁的时候根据Key获取到的Value与本地记录的Value是否一致,不一致则不进行删除
防止Key过期:
我们可以使用Watch Dog(看门狗)解决;
有一个小狗狗专门负责监控哪个服务进行上锁;
如果到期时间到了之后还没有释放所,那么小狗狗将对这把锁进行续期,延长过期时间
3.2 性能分析
上述使用Redis实现了分布式锁防止超卖
但是如果在千万级并发的情况下,几千甚至几万个请求同时去Redis中抢一把锁
可想而知,对性能指标来说是不达标的
分段锁
分段锁的实现方案,是将一个商品的锁分成多个
案例:
假设商品A的库存有100件,此时我们可以将库存分成10份,每份10个单位。分别对应10
个锁,这样将每个请求分发到不同的单位锁中。从原来的一把锁,拓展到了十把锁,
性能可以提高十倍
3.3 Redis集群问题
一般的生产环境上,Redis为了可用性肯定也是会做集群化,此时会引发一个新的问题。
问题描述:
如果说服务A在Redis中上锁Key1,Redis返回上锁成功后此时对Slave进行同步数据时
Master挂掉了之后,选取出新的Master中,并没有原来Master中的Key1这把锁。
此时另外的服务进来就可以继续上锁,有可能引发超卖
Redis红锁
基于上述的Redis集群问题,我们可以使用红锁进行解决
所谓红锁,就是部署多个独立的Redis,每次上锁都往这些Redis中上锁
如果服务A上锁成功的数量超过Redis的一半,那么则为上锁成功!
此时另外的服务B同时向这几台Redis上锁,如果超过一半以上Redis上锁失败
则认定获取锁失败。
红锁问题
问题描述:
如果在服务A上锁后的过程中,其中一台的Redis挂掉了。运维人工马上又开启了
一台新的Redis,此时五台Redis中只有两台Redis存在锁(不过半),那么另外的
服务B进行就可以上锁成功,就会可能产生超卖
3.4 还有一个问题
在基于上面的Redis加上看门狗的机制下,万一此时的JVM内存满了产生STW
那么看门狗也会无法正常监控,也有可能导致锁过期,导致另外的服务可以上锁
从而产生超卖现象
那么基于上面的Redis解锁分布式锁的多个演变过程,可以得出以下几点结论
1、针对于中小型企业来说,如果并发量不高,其实Redis已经可以解决锁问题
2、Redis实现的红锁、看门狗、分段锁等解决方案虽然可以解决,但是实现的成本高
3、基于红锁的问题和3.4提出的JVM内存产生STW,不能很好的解决分布式锁的问题
那么接下来,我们将会用最后一个存储方案解决分布式锁的问题,那就是Zookeeper!
四、Zookeeper实现分布式锁
Zookeeper是Apache开发维护的一个开源服务器,以实现高度可靠的分布式协调方案
根据ZK的两个特性
1、顺序节点存储
2、临时节点
基于ZK的两大特性,就可以很方便的解决以下问题防止超卖
1、顺序节点:
上锁后程序保存好的顺序节点值,在后续解锁时,如果跟保存的顺序节点值一样则断开会话连接(解锁)
防止删除另外程序的锁导致的超卖问题
2、临时节点:
临时节点的有效性是基于会话的。会话一旦结束,则自动解锁。并且如果在服务中产生STW
那么与ZK的会话连接也会断开,有效的防止了死锁的情况,也不需要使用看门狗机制防止锁过期
基于三种方案的分布式锁实现就先讲到这里
如果有疑问或者文章中有需要修正的
欢迎大家私聊