分布式锁(2) —— Redisson实现分布式锁 (3)

RedissonLock 同样没有解决 节点挂掉的时候,存在丢失锁的风险的问题。而现实情况是有一些场景无法容忍的,所以 Redisson 提供了实现了redlock算法的 RedissonRedLock,RedissonRedLock 真正解决了单点失败的问题,代价是需要额外的为 RedissonRedLock 搭建Redis环境。

所以,如果业务场景可以容忍这种小概率的错误,则推荐使用 RedissonLock, 如果无法容忍,则推荐使用 RedissonRedLock。

几个问答

1.为什么RedissonLock是可重入的锁?

存储锁的数据结构是hashmap而不是string,锁名:{thread标识:计数值},增减计数值使用lua脚本进行处理。

RedLock

由于Redis的主从是使用异步的同步机制(slaveof,fork子进程生成RDB文件,之后AOF同步)。所以无论使用哪种部署形式,在master down掉的情况下,锁都是不可靠的(master未同步至slave, 主从切换后另外一个线程在slave获取锁)。所以Redis官方给出了RedLock算法的解决方案。大体原理就是少数服从多数。

假设我们有N个Redis master节点,这些节点都是完全独立的,我们不用任何复制或者其他隐含的分布式协调机制。之前我们已经描述了在Redis单实例下怎么安全地获取和释放锁。我们确保将在每(N)个实例上使用此方法获取和释放锁。在我们的例子里面我们把N设成5,这是一个比较合理的设置,所以我们需要在5台机器上面或者5台虚拟机上面运行这些实例,这样保证他们不会同时都宕掉。为了取到锁,客户端应该执行以下操作:

获取当前Unix时间,以毫秒为单位。

依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。当向Redis请求获取锁时,客户端应该设置一个尝试从某个Reids实例获取锁的最大等待时间(超过这个时间,则立马询问下一个实例),这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。

客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁消耗的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的总耗时小于锁失效时间时,锁才算获取成功。

如果取到了锁,key的真正有效时间 = 有效时间(获取锁时设置的key的自动超时时间) – 获取锁的总耗时(询问各个Redis实例的总耗时之和)(步骤3计算的结果)。

如果因为某些原因,最终获取锁失败(即没有在至少 “N/2+1 ”个Redis实例取到锁或者“获取锁的总耗时”超过了“有效时间”),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,这样可以防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

Redisson实现RedLock

这里以三个单机模式为例子,他们是完全相互独立的。

public static void RedLockTest() { Config config1 = new Config(); config1.useSingleServer().setAddress("redis://127.0.0.1:5379") .setPassword("123456").setDatabase(0); RedissonClient redissonClient1 = Redisson.create(config1); Config config2 = new Config(); config2.useSingleServer().setAddress("redis://127.0.0.1:5380") .setPassword("123456").setDatabase(0); RedissonClient redissonClient2 = Redisson.create(config2); Config config3 = new Config(); config3.useSingleServer().setAddress("redis://127.0.0.1:5381") .setPassword("123456").setDatabase(0); RedissonClient redissonClient3 = Redisson.create(config3); /** * 获取多个 RLock 对象 */ RLock lock1 = redissonClient1.getLock("anyLock"); RLock lock2 = redissonClient2.getLock("anyLock"); RLock lock3 = redissonClient3.getLock("anyLock"); /** * 根据多个 RLock 对象构建 RedissonRedLock (最核心的差别就在这里) */ RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3) try { /** * 4.尝试获取锁 * waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败 * leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间, * 确保在锁有效期内业务能处理完) */ boolean res = redLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS); if (res) { //成功获得锁,在这里处理业务 } } catch (Exception e) { throw new RuntimeException("aquire lock fail"); }finally{ //无论如何, 最后都要解锁 redLock.unlock(); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwzdzf.html