Redlock算法-Redis官方分布式锁

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redlock算法-Redis官方分布式锁相关的知识,希望对你有一定的参考价值。

参考技术A 在分布式版本的算法中,我们假设有N个Redis实例,这些节点是完全独立的,因此,我们不需要复制或任何其他的协调方法。在单机模式中,我们已经描述了怎样安全地取得和释放锁。我们认为Redlock算法在单机模式中将会使用这种方法去取得和释放锁是理所应当地。在我们地例子中N=5,这是一个合理地值,因此需要在5个不同的电脑或虚拟机上运行5个Redis实例以确保他们之间地相互独立。

为了取得锁,客户端执行以下操作:

1. 获取当前毫秒级时间Start。

2. 试着用相同的key和value顺序地对N个Redis实例进行加锁。该步骤中,在为每个实例设置锁的同时,客户端会使用一个超时时间timeout,timeout小于锁的自动释放时间ValidityTime。例如:如果ValidityTime=10s,则timeout应该在5~50ms之间。这可以使一个实例宕机时,客户端阻塞的时间更短,当一个Redis实例不可达时,应该尽快的去对另一个实例加锁。

3. 获取当前毫秒级时间End, End - Start = Elapsed, Elapsed是客户端为了获得锁而花费的时间。当且仅当客户端在所有实例中取得的锁的个数占多数,并且花费的总时间Elapsed小于锁的有效时间,此时,才会认为加锁成功。

4. 如果加锁成功,锁的有效时间则是ValidityTime = InitTime-Elapsed,Elapsed如步骤3中计算。

5. 如果客户端由于某些原因加锁失败,如ValidityTime<=0 或者 n < N/2 + 1。此时,客户端将会对这些实例进行unlock操作。

我们需要更好的说明这个排他锁的规则:只有当客户端在有效时间ValidityTime内完成工作才能保证该排他锁的有效性。

实战:Redis集群环境下的-RedLock(真分布式锁)

每天为您推送优质技术文章

在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增加一点复杂的设计就可以获得更好的可靠性。 这篇文章的目的就是尝试提出一种官方权威的用Redis实现分布式锁管理器的算法,我们把这个算法称为RedLock。

Redlock是redis官方提出的实现分布式锁管理器的算法。这个算法会比一般的普通方法更加安全可靠。关于这个算法的讨论可以看下官方文档。

https://github.com/antirez/redis-doc/blob/master/topics/distlock.md

安全和可靠性保证

在描述我们的设计之前,我们想先提出三个属性,这三个属性在我们看来,是实现高效分布式锁的基础。

1、一致性:互斥,不管任何时候,只有一个客户端能持有同一个锁。 2、分区可容忍性:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。 3、可用性:只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。

为什么基于故障切换的方案不够好

为了理解我们想要提高的到底是什么,我们先看下当前大多数基于Redis的分布式锁三方库的现状。 用Redis来实现分布式锁最简单的方式就是在实例里创建一个键值,创建出来的键值一般都是有一个超时时间的(这个是Redis自带的超时特性),所以每个锁最终都会释放。

而当一个客户端想要释放锁时,它只需要删除这个键值即可。 表面来看,这个方法似乎很管用,但是这里存在一个问题:在我们的系统架构里存在一个单点故障,如果Redis的master节点宕机了怎么办呢?有人可能会说:加一个slave节点!在master宕机时用slave就行了!但是其实这个方案明显是不可行的,因为这种方案无法保证第1个安全互斥属性,因为Redis的复制是异步的。 总的来说,这个方案里有一个明显的竞争条件(race condition),举例来说:

1、客户端A在master节点拿到了锁。 2、master节点在把A创建的key写入slave之前宕机了。 3、slave变成了master节点 4、B也得到了和A还持有的相同的锁(因为原来的slave里还没有A持有锁的信息)

当然,在某些特殊场景下,前面提到的这个方案则完全没有问题,比如在宕机期间,多个客户端允许同时都持有锁,如果你可以容忍这个问题的话,那用这个基于复制的方案就完全没有问题,否则的话我们还是建议你采用这篇文章里接下来要描述的方案。

Redlock 简介

在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。实现高效的分布式锁有三个属性需要考虑:

1、安全属性:互斥,不管什么时候,只有一个客户端持有锁 2、效率属性A:不会死锁 3、效率属性B:容错,只要大多数redis节点能够正常工作,客户端端都能获取和释放锁。

Redlock 算法

在分布式版本的算法里我们假设我们有N个Redis master节点,这些节点都是完全独立的,我们不用任何复制或者其他隐含的分布式协调算法。我们已经描述了如何在单节点环境下安全地获取和释放锁。因此我们理所当然地应当用这个方法在每个单节点里来获取和释放锁。在我们的例子里面我们把N设成5,这个数字是一个相对比较合理的数值,因此我们需要在不同的计算机或者虚拟机上运行5个master节点来保证他们大多数情况下都不会同时宕机。一个客户端需要做如下操作来获取锁:

1、获取当前时间(单位是毫秒)。

2、轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。

3、客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。

4、如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。

5、如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

Redisson 实现方式(红锁 RedLock)

github Redisson https://github.com/redisson/redisson

Maven

    
      
      
    
  1. <!-- JDK 1.8+ compatible -->

  2. <dependency>

  3. <groupId>org.redisson</groupId>

  4. <artifactId>redisson</artifactId>

  5. <version>3.9.0</version>

  6. </dependency>


  7. <!-- JDK 1.6+ compatible -->

  8. <dependency>

  9. <groupId>org.redisson</groupId>

  10. <artifactId>redisson</artifactId>

  11. <version>2.14.0</version>

  12. </dependency>

集群模式配置

集群模式除了适用于Redis集群环境,也适用于任何云计算服务商提供的集群模式,例如AWS ElastiCache集群版、Azure Redis Cache和阿里云(Aliyun)的云数据库Redis版。

程序化配置集群的用法:

    
      
      
    
  1. @Bean

  2. public RedissonClient redissonClient() {

  3. Config config = new Config();

  4. config.useClusterServers()

  5. .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒

  6. //可以用"rediss://"来启用SSL连接

  7. .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")

  8. .addNodeAddress("redis://127.0.0.1:7002");

  9. return Redisson.create(config);

  10. }

基于Redis的Redisson红锁 RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个 RLock对象关联为一个红锁,每个 RLock对象实例可以来自于不同的Redisson实例。

    
      
      
    
  1. RLock lock1 = redissonClient1.getLock("lock1");

  2. RLock lock2 = redissonClient2.getLock("lock2");

  3. RLock lock3 = redissonClient3.getLock("lock3");


  4. RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);

  5. // 同时加锁:lock1 lock2 lock3

  6. // 红锁在大部分节点上加锁成功就算成功。

  7. lock.lock();

  8. ...

  9. lock.unlock();

Redisson 监控锁

大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了 leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

    
      
      
    
  1. RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);

  2. // 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开

  3. lock.lock(10, TimeUnit.SECONDS);


  4. // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开

  5. boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

  6. ...

  7. lock.unlock();


长按二维码关注「Java技术实战」

点个在看再走呗!

以上是关于Redlock算法-Redis官方分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

实战:Redis集群环境下的-RedLock(真分布式锁)

Redis RedLock 完美的分布式锁么?

基于Redis的分布式锁算法RedLock及RedLock-Hyperf实现

RedLock算法-使用redis实现分布式锁服务

基于Redis的分布式锁和Redlock算法

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法