Redis分布式锁
介绍
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。
- 获取锁
Shell
# 添加锁,NX是互斥、EX是设置超时时间
SET lock value NX EX 10
- 释放锁
Shell
# 释放锁,删除即可
DEL key
Redis实现分布式锁该如何合理的控制锁的有效时长
可能产生的问题
当线程获取锁成功后,设置了过期时间,如果业务执行的时间太长了,超过了锁的失效时间,锁就会自动释放了,但是业务还没有执行完成,这时有其他线程就可以获取锁成功。此时,就无法保证业务执行的原子性了。
解决方法(Redisson实现的分布式锁-执行流程)
- 获取锁:当线程1进入后,获取锁,如果获取成功,直接操作Redis。
- WatchDog:如果获取锁成功后,会另外开启一个线程进行监控,WatchDog(看门狗)。
- 续期操作:它会不断监听这个持有锁的线程,给这个线程增加锁的持有时间(续期操作)。
- 续期默认值:每隔(releaseTime / 3)的时间做一次续期,releaseTime就是锁的过期时间,默认为30秒。默认每隔10秒做一次续期。
- 释放锁:业务执行完成后,手动释放锁,并且会通知WatchDog,不需要再监听了。
- 竞争情况:在这个过程中,如果有其他线程2尝试加锁,如果加锁成功,流程如上。
- 重试机制:在redis中,如果加锁失败,就中断线程2了;在redisson中,设置了一个while循环,不断尝试获取锁,如果在很短时间内,线程1释放了锁,线程2就可以立马获取锁了。
- 结果:如果线程1一直没有释放锁,线程2也不会一直循环,redisson设置了阈值,循环一定次数,也会中断。一般情况下线程2不会等很久。
动图示例
代码实现
加锁、设置过期时间等操作都是基于lua脚本完成的
java
public void redisLock() throws InterruptedException {
//获取锁 (重入锁),执行锁的名称
RLock lock = redissonClient.getLock("heimalock");
try {
//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
// boolean isLock = lock.tryLock(10,30, TimeUnit.SECONDS);
boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
//判断是否获取成功
if (isLock) {
System.out.println("执行业务...");
}
} finally {//释放锁
lock.unlock();
}
}
Redisson实现的分布式锁--可重入
代码示例
java
public void add1() {
RLock lock = redissonClient.getLock("heimalock");
boolean isLock = locktryLock();
//执行业务
add2();
//释放锁
lock.unlock();
}
public void add2() {
RLock lock = redissonClient.getLock("heimalock");
boolean isLock = lock.tryLock();
// 执行业务
// 释放锁
lock.unlock();
}
可重入动图示例
利用的hash结构记录的线程id和重入次数
- Key就是锁名称,value存储的时当前线程重入的次数
- 其中field为线程id,value为线程重入次数
具体实现说明
- 当线程执行方法1时,获取锁后,记录当前线程id,和入锁次数1.
- 当执行方法2时,获取锁后,发现有当前线程id,入锁次数+1,为2.
- 当方法2释放锁,入锁次数-1,为1.
- 当方法2执行完成,方法1释放锁,入锁次数-1,为0,此时删除Key信息。
redisson实现的分布式锁--主从一致性
问题场景
- 如果搭建了主从架构,一个主节点,两个从节点,主节点负责增删改操作,从节点负责读操作,主节点进行主从同步。
- 当创建了分布式锁,因为是写数据,找到主节点,进行写操作,正常情况下,主节点要将数据同步给从节点,假如主节点没来得及同步,宕机了。
- 依据redis的哨兵机制,两个从节点进行选举,当其中一个被选做主节点
- 此时又一个线程进行创建分布式锁,发现新的主节点中没有,此时又创建锁成功,执行业务,导致了脏数据的产生。
问题示例
解决方法1--红锁
- RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n / 2 + 1),避免在一个redis实例上加锁。
- n代表节点数量,如果有三台,创建锁的节点至少大于等于2.
缺点
- 实现复杂
- 高并发下,性能差
- 运维繁琐
解决方法2--zookeeper
- redis采用的是AP思想,高可用,可以保证最终一致
- 如果业务要求强一致性,可以采用zookeeper的分布式锁,采用的时CP思想,强一致。