分布式锁讲解
为什么需要分布式锁?
场景问题: 超卖问题
在此种情形下,没有做锁的处理会出现一种情况,例如:
一.Synchroized锁能否解决问题
我们需要明白一点:Synchroized锁是JVM内置的锁
许多项目都是集群架构,若加入Synchroized这种JDK级别和JVM级别的锁时不能的
如下图:如果我们使用nginx负载均衡,把请求发到不同的服务器,如第一个请求在tomcat1上,第二个请求在tomcat2上,这样Synchroized锁是不同的JVM上的,tomcat1的锁是用不了在tomcat2上的。
这种情况只有在高并发的情况下才会出现
所以 Synchroized是解决不了问题的
二.分布式锁
利用redis setnx来做分布式锁
因为redis是单线程操作,不管是不是单线程操作,都是要排队的
但是这么设置会有许多问题,例如异常,宕机怎么办?
处理方法
1.加入异常处理
try catch finally
新的问题:如果系统重启呢?没有删除掉key
2.设置超时时间
这样写没有原子性,两行代码,如第一个设置的key没有怎么办
设置过期时间(也会出现问题,线程乱删除锁)
这种情况只要出现一次,锁就会失效!!!
处理方法:
- 先加锁(设置随机值)
- 比较锁的值(若相同则就是自己的锁,不同就是别人的锁)
**场景问题:**网络卡顿的情况
3.锁续命
自动检测锁的过期时间
逻辑:在主线程里面开一个定时任务(分线程),定时检测锁的过期时间 ,如果要过期了就续命
redisson
分布式锁中的王者方案-Redisson - 悟空聊架构 - 博客园 (cnblogs.com)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.8</version>
</dependency>
初始化
// 1. Create config object
Config config = new Config();
config.useClusterServers()
// use "rediss://" for SSL connection
.addNodeAddress("redis://127.0.0.1:7181");
// or read config from file
config = Config.fromYAML(new File("config-file.yaml"));
使用
// 4. Get Redis based implementation of java.util.concurrent.locks.Lock
RLock lock = redisson.getLock("myLock");
RLockReactive lockReactive = redissonReactive.getLock("myLock");
RLockRx lockRx = redissonRx.getLock("myLock");
redisson分布式锁实现原理
4…Lua脚本设置分布式锁:
- 在服务器端
- 优点:在后期使用直接调用
- 缺点:修改不方便,占用资源
- 在java端:
- 优点:灵活
- 缺点:每次都要发送lua脚本去服务器端,可能会造成网络通信延长
注意:使用了redis锁 可以保持可靠性,但会降低并发
缓存数据库双写不一致
缓存不一致
延迟双删 : 删缓存不止删一次,要删两次
缺点:
- 能在一定程度上解决,但确定不了sleep的时间,无法确定是否能准确删除
- 每一次修改数据都要删除两次缓存,提供了性能消耗。
内存队列:将同一个商品的增删查改操作存到一个队列里
并发问题串行化
难以实现,维护很多队列,容易OOM
加分布式锁(悲观锁)
都是操作库存Stock,共享资源
加了分布式锁可以,让并发问题串行化,比内存队列很容易维护
缺点:影响效率,每一次操作都会加锁阻塞,性能会非常低
加读写锁
大多数用户操作都是读多写少
读锁不互斥 写锁互斥
redisson 自带读写锁