一、使用分布式锁的背景是什么
1、如果你公司的业务,各个应用都只部署了一台机器,那么完全用不着分布式锁,直接使用Java的锁即可
2、可是当你们的业务量大,多台机器并发情况下争夺一个资源的时候,就必须要保证业务的原子性了。
举例:
例子1、超卖问题:一共100个商品待抢购,当还有最后一个库存的时候,实例1,2,3.....10都查询到还有库存,用户同时都下单成功;那么就出现了超卖问题:你一共100的库存,哪来的110个商品发货?
怎么解决呢?将100个商品写入redis中,并加上分布式锁,必须一个实例,拿到锁,并完成交易,扣除库存后,释放锁,才能让下一个实例拿到锁;这样就保证了不会超卖
例子2、定时任务问题:多台分布式应用,要执行对账,如果多台机器同时执行,就有可能产生脏数据;如果使用分布式锁,使同时只有一个应用实例去执行一个任务,这样就实现了互斥,不会导致脏数据产生
二、Redis分布式锁,是如何实现的?
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则set)的简写
获取锁:
#添加锁,lock是锁的名称,value就是值(根据业务命名) NX代表互斥,EX是设置超时时间
SET lock value NX EX 10
释放锁:
# 释放锁,删除即可
DEL key
分布式锁必须加超时时间的原因是,如果应用获取到锁后,宕机了,那么就永远释放不了锁了
三、Redisson实现分布式锁,如何控制锁的有效时长?
如果应用使用了分布式锁,但是在锁的有效时间都超过了(锁失效了),那么应用的其他实例就会拿到锁,也去执行业务代码,就有可能会导致脏数据产生
那么如何合理控制锁的有效时长呢?
1、根据业务预估
根据业务代码评估后,觉得实例拿到锁之后,多久能跑完,就给锁设值多久过期
但是这样很不准确
2、动态地给锁续期
我只要拿到锁的线程还在跑,那我就一直给锁加有效时间(超时时间)即可,每次加10秒钟,诶,非常合理
有以下两种方式:
1、自己代码里新开一个线程,每隔10秒或者多少秒,延长一下锁的有效时间(过期时间)
或者说,在数据库标记一个标识,新开一个线程记录当前线程id + 状态,根据状态判断是否需要延期
2、使用Redisson的看门狗机制
Redisson自带有一个watch dog(看门狗机制),
如图所示,
1、实例1在获取到锁后,在加锁后,Redisson会另外开一个线程(看门狗线程),监控持有锁的线程,会不断增加持有锁的线程的持有锁的时间(超时时间,默认10秒)
看门狗线程,每次续期(release / 3)的时间,其中release是锁的默认超时时间:30s,也就是说默认10秒钟续期一次,每次重新设置为30秒过期时间
当实例1释放锁的时候,需要通知一次看门狗,不需要再做监听了,因为key已经被删除了
2、在实例2,尝试加锁的过程中,如果加锁失败,会循环,尝试获取锁;
当然实例2也不会无限循环尝试获取,一段时间没有加锁成功就会报错,这就是Redisson新增的重试机制
public void redisLock() throws InterruptedException{// 获取锁(重入锁),执行锁的名称RLok lock = redissonClient.getLock("myLockName");// 尝试获取锁,参数分别是:1、获取锁的最大等待时间(就是未获取到锁循环尝试获取的时间)// 2、锁自动释放时间(过期时间),如果你设置了这个值,那么Watch dog(看门狗)就不会生效,因为他会认为你能控制锁的超时时间,不会定时给你续期// 3、第三个参数为 前两个时间参数的单位// boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);// 判断是否获取锁成功if(isLock) {try {System.out.prientln("执行你的业务逻辑");} finally {// 释放锁lock.unlock();}}
}
所有的加锁解锁,设置过期时间,看门狗续期等,都是根据lua脚本来实现的,保证执行的原子性
四、Redisson实现的分布式锁,可以重入吗?
为什么同一个线程要多次拿到锁呢?
因为如果一个任务,根据不同的传参,可能有不同的判断,要调用不同的方法,调用不同的方法耗时可能不同,那么就可以使用重入机制,调一个方法就延长一下锁的有效时长;
所以问题的答案是:Redisson实现的分布式锁可以重入,redission中可以记录线程id,使同一线程的不同方法,都可以重复拿到锁
如图:add1方法获取锁后,Redis会使用hash结果记录锁的key,获取到锁的线程id、以及重入次数
当add1方法调用add2方法后,add2方法也可以获取到锁,这时Redis会把重入次数加一;
当add2释放锁之后,重入次数会减一
当add1释放锁之后,重入次数也会减一,当重入次数value=0时,Redis就会把这个锁删除
五、Redission可以解决Redis主从不一致的问题吗?
什么叫主从不一致的问题?
如果说,你的应用实例1在主节点写入了一个分布式锁,这时候其他实例2,3,4是拿不到分布式锁的;
而如果在你实例1还在执行的时候,Redis主节点挂了(宕机了)!这时候呢,Redis的一个从节点会自动升级为主节点,但是这个新的从节点,里面是没有这个分布式锁的,那么实例2,3,4就又可以获得一个锁,来进行代码的执行,这就有可能导致脏数据问题
所以:Redisson分布式锁,是不能解决主从一致性问题的。
但是,Redis中有另一种解决方案,叫做红锁(redlock)机制,就是把分布式锁的信息保存在X台(X = Redis节点数 / 2 + 1,即超过半数)Redis的节点上;每次加锁,都必须拿到这X台(超过半数)机器的锁,才算真正获得锁
红锁:
但是红锁,一般是不会使用的,因为这样性能太低了,如果硬是要保持数据强一致性,还可以使用zookeeper实现的分布式锁,或者其他机制