个人博客


之前使用redisson的普通分布式锁方案不能解决对于集群或者哨兵模式下的主从切换场景导致锁丢失的问题。redisson还对redlock算法进行了封装,可以解决主从切换导致的锁丢失问题。

需要注意的是,只有充分了解普通分布式锁是如何实现的,才能更好的了解redlock分布式锁的实现,因为redlock分布式锁的实现完全基于普通分布式锁。

1、Redlock算法

在Redis的分布式环境中,我们假设有N个完全互相独立的Redis节点,在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。(可以是N个单机节点,也可以是N个sentinel或者是N个cluster集群)

现在假设有5个Redis主节点(大于等于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程中,客户端会执行以下操作:

  • 获取当前Unix时间,以毫秒为单位。
  • 依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。
  • 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
  • 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  • 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

2、Redlock示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Test
public void testDisLock() {
// 配置多台互相独立的redis单机节点
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://172.16.122.104:6379").setDatabase(0);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://172.16.122.104:6380").setDatabase(0);
Config config3 = new Config();
config3.useSingleServer().setAddress("redis://172.16.122.104:6381").setDatabase(0);

// 构建RedissonClient
RedissonClient redissonClient1 = Redisson.create(config1);
RedissonClient redissonClient2 = Redisson.create(config2);
RedissonClient redissonClient3 = Redisson.create(config3);

// 5个线程并发去获取锁
IntStream.range(0, 5).parallel().forEach(i -> tryRedlock(redissonClient1, redissonClient2, redissonClient3));
}

@SneakyThrows
private void tryRedlock(RedissonClient... redissonClients) {
// 构建Redlock对象
RLock[] rLock = new RLock[redissonClients.length];
for (int i = 0; i < rLock.length; i++) {
rLock[i] = redissonClients[i].getLock("redlock");
}
RLock redLock = new RedissonRedLock(rLock);

// 基于Redlock对象去操作,与redisson实现普通的分布式锁一样
// 获取锁最多等待500ms,10s后key过期自动释放锁
boolean tryLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
if (tryLock) {
// 获取到锁后开始执行对资源的操作
try {
log.info("当前线程:[{}]获得锁", Thread.currentThread().getName());
// 操作资源...
} finally {
redLock.unlock();
}
} else {
log.info("当前线程:[{}]没有获得锁", Thread.currentThread().getName());
}
}

redisson的普通分布式锁相比,只是会有多个RedissonClient(每个对应1个redis节点)构建出多个RLock对象,再由这多个RLock对象构建成RedissonRedLock对象。由这个对象去获取和释放锁的步骤与用redisson的普通分布式锁步骤一模一样。

因为redlock是基于多节点的redis来实现的,其中1个节点故障并不影响其它节点持有锁,所以redlock的可靠性来的更高。

参考链接

关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和redis的作者antirez之间已经发生过一场争论,下面是神仙打架的文章地址:

代码地址