在不一样进程须要互斥地访问共享资源时,分布式锁是一种很是有用的技术手段。 有不少三方库和文章描述如何用Redis实现一个分布式锁管理器,可是这些库实现的方式差异很大,并且不少简单的实现其实只需采用稍微增长一点复杂的设计就能够得到更好的可靠性。 这篇文章的目的就是尝试提出一种官方权威的用Redis实现分布式锁管理器的算法,咱们把这个算法称为RedLock。html
Redlock是redis官方提出的实现分布式锁管理器的算法。这个算法会比通常的普通方法更加安全可靠。关于这个算法的讨论能够看下官方文档。java
https://github.com/antirez/re...git
在描述咱们的设计以前,咱们想先提出三个属性,这三个属性在咱们看来,是实现高效分布式锁的基础。github
一、一致性:互斥,无论任什么时候候,只有一个客户端能持有同一个锁。
二、分区可容忍性:不会死锁,最终必定会获得锁,就算一个持有锁的客户端宕掉或者发生网络分区。
三、可用性:只要大多数Redis节点正常工做,客户端应该都能获取和释放锁。redis
为了理解咱们想要提升的究竟是什么,咱们先看下当前大多数基于Redis的分布式锁三方库的现状。 用Redis来实现分布式锁最简单的方式就是在实例里建立一个键值,建立出来的键值通常都是有一个超时时间的(这个是Redis自带的超时特性),因此每一个锁最终都会释放。算法
而当一个客户端想要释放锁时,它只须要删除这个键值便可。 表面来看,这个方法彷佛很管用,可是这里存在一个问题:在咱们的系统架构里存在一个单点故障,若是Redis的master节点宕机了怎么办呢?有人可能会说:加一个slave节点!在master宕机时用slave就好了!可是其实这个方案明显是不可行的,由于这种方案没法保证第1个安全互斥属性,由于Redis的复制是异步的。 总的来讲,这个方案里有一个明显的竞争条件(race condition),举例来讲:数据库
一、客户端A在master节点拿到了锁。
二、master节点在把A建立的key写入slave以前宕机了。
三、slave变成了master节点
四、B也获得了和A还持有的相同的锁(由于原来的slave里尚未A持有锁的信息)缓存
固然,在某些特殊场景下,前面提到的这个方案则彻底没有问题,好比在宕机期间,多个客户端容许同时都持有锁,若是你能够容忍这个问题的话,那用这个基于复制的方案就彻底没有问题,不然的话咱们仍是建议你采用这篇文章里接下来要描述的方案。安全
在不一样进程须要互斥地访问共享资源时,分布式锁是一种很是有用的技术手段。实现高效的分布式锁有三个属性须要考虑:服务器
一、安全属性:互斥,无论何时,只有一个客户端持有锁
二、效率属性A:不会死锁
三、效率属性B:容错,只要大多数redis节点可以正常工做,客户端端都能获取和释放锁。
在分布式版本的算法里咱们假设咱们有N个Redis master节点,这些节点都是彻底独立的,咱们不用任何复制或者其余隐含的分布式协调算法。咱们已经描述了如何在单节点环境下安全地获取和释放锁。所以咱们理所固然地应当用这个方法在每一个单节点里来获取和释放锁。在咱们的例子里面咱们把N设成5,这个数字是一个相对比较合理的数值,所以咱们须要在不一样的计算机或者虚拟机上运行5个master节点来保证他们大多数状况下都不会同时宕机。一个客户端须要作以下操做来获取锁:
一、获取当前时间(单位是毫秒)。
二、轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每一个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。好比若是锁自动释放时间是10秒钟,那每一个节点锁请求的超时时间多是5-50毫秒的范围,这个能够防止一个客户端在某个宕掉的master节点上阻塞过长时间,若是一个master节点不可用了,咱们应该尽快尝试下一个master节点。
三、客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),并且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
四、若是锁获取成功了,那如今锁自动释放时间就是最初的锁释放时间减去以前获取锁所消耗的时间。
五、若是锁获取失败了,不论是由于获取成功的锁不超过一半(N/2+1)仍是由于总消耗时间超过了锁释放时间,客户端都会到每一个master节点上释放锁,即使是那些他认为没有获取成功的锁。
github Redisson
https://github.com/redisson/r...
<!-- JDK 1.8+ compatible --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.9.0</version> </dependency> <!-- JDK 1.6+ compatible --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>2.14.0</version> </dependency>
集群模式除了适用于Redis集群环境,也适用于任何云计算服务商提供的集群模式,例如AWS ElastiCache集群版、Azure Redis Cache和阿里云(Aliyun)的云数据库Redis版。
程序化配置集群的用法:
@Bean public RedissonClient redissonClient() { Config config = new Config(); config.useClusterServers() .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒 //能够用"rediss://"来启用SSL链接 .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001") .addNodeAddress("redis://127.0.0.1:7002"); return Redisson.create(config); }
基于Redis的Redisson红锁RedissonRedLock
对象实现了Redlock介绍的加锁算法。该对象也能够用来将多个RLock
对象关联为一个红锁,每一个RLock
对象实例能够来自于不一样的Redisson实例。
RLock lock1 = redissonClient1.getLock("lock1"); RLock lock2 = redissonClient2.getLock("lock2"); RLock lock3 = redissonClient3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); // 同时加锁:lock1 lock2 lock3 // 红锁在大部分节点上加锁成功就算成功。 lock.lock(); ... lock.unlock();
你们都知道,若是负责储存某些分布式锁的某些Redis节点宕机之后,并且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了不这种状况的发生,Redisson内部提供了一个监控锁的看门狗,它的做用是在Redisson实例被关闭前,不断的延长锁的有效期。默认状况下,看门狗的检查锁的超时时间是30秒钟,也能够经过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还经过加锁的方法提供了leaseTime
的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); // 给lock1,lock2,lock3加锁,若是没有手动解开的话,10秒钟后将会自动解开 lock.lock(10, TimeUnit.SECONDS); // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); ... lock.unlock();