一、分布式锁会有哪些问题?
在前面的章节中我们了解能够使用Redission实现分布式锁了,并且使用Watch Dog机制实现锁的自动续期。但是细心的同学可以发现,这种加锁实际上是存在一些问题的:
- 在使用单节点Redis实现分布式锁时,如果这个Redis实例宕机了的话,那么所有使用这个实例的客户端都会出现无法获取锁的情况。
- Redis主从模式下,如果客户端在master节点拿到了锁,但是这个时候master节点挂掉了,锁的信息还没有来得及同步给slave节点。(大家都知道,主从模式下master和slave时通过异步的方式进行数据同步的。)出现这种情况时,客户端的锁信息将会丢失。
二、RedLock解决了什么问题?
1.简介
为了解决分布式锁存在的以上问题,Redis的作者提出了RedLock算法,能够有效防止单点故障的问题。RedLock通过使用多个Redis节点,来提供一个更健壮的分布式锁解决方案,能够在某些Redis节点故障的情况下,仍然能够保证分布式锁的可用性。
2.原理
使用RedLock时,在进行加锁的时候RedLock会向每个Redis节点都发送相同的命令,每个节点都会去进行锁竞争。如果在大多数节点上都获得了锁,那么就认为加锁成功。反之,如果没有在大多数节点都加锁成功,则认为加锁失败。这样就可以避免由于某个Redis节点故障导致加锁失败的情况。
3.RedLock节点的要求
- 需要有多个Redis的master节点
- 这些节点完全相互独立
- 这些节点不存在主从复制
- 这些节点不存在集群协调机制
根据这几点要求其实就说明了,这些master不能在一个集群中。
三、怎么使用RedLock?
1. 使用Redisson客户端为例,引入Redisson依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
2.创建Redisson客户端
@Configuration
public class RedLockConfiguratoin {
@Bean(name = "redisson1",destroyMethod = "shutdown")
public RedissonClient redisson1(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6382");
return Redisson.create(config);
}
@Bean(name = "redisson2",destroyMethod = "shutdown")
public RedissonClient redisson2(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6383");
return Redisson.create(config);
}
@Bean(name = "redisson3",destroyMethod = "shutdown")
public RedissonClient redisson3(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6384");
return Redisson.create(config);
}
}
3.在业务处理中使用RedLock
@Service
public class OrderService {
@Resource
private RedissonClient redisson1;
@Resource
private RedissonClient redisson2;
@Resource
private RedissonClient redisson3;
@GetMapping("/tryLock")
public void testLock() {
// 获取多个锁
RLock lock1 = redisson1.getLock("myLock");
RLock lock2 = redisson2.getLock("myLock");
RLock lock3 = redisson3.getLock("myLock");
// 获取RedLock对象
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
/**
* 4.尝试获取锁
* redLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS)
* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
* leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
*如果不设置waitTimeout和leaseTime则会触发看门狗自动续期机制
*/
boolean isLocked = redLock.tryLock();
if (isLocked) {
// 成功获取到锁
log.debug("线程: {} 获取锁成功", Thread.currentThread().getName());
// 模拟业务处理耗时
TimeUnit.SECONDS.sleep(40);
// 执行需要加锁的代码逻辑
// ...
} else {
log.debug("线程: {} 获取锁失败", Thread.currentThread().getName());
// 获取锁失败
// 处理获取锁失败的逻辑
// ...
}
} catch (InterruptedException e) {
// 处理中断异常
log.error(e.getMessage(), e);
} finally {
// 释放锁
redLock.unlock();
log.debug("线程: {} 释放锁成功", Thread.currentThread().getName());
}
}