Redis 分布式锁原理,能够直接看官方文档:
https://redis.io/commands/set...java
The command
SET resource-name anystring NX EX max-lock-time
is a simple way to implement a locking system with Redis.
SET resource-name anystring NX EX max-lock-time
命令能够基于 Redis 实现分布式锁。git
NX
Only set the key if it does not already existEX seconds
Set the specified expire time, in seconds
NX
仅当 key 不存在时设置成功EX seconds
失效时间(秒)A client can acquire the lock if the above command returnsOK
(or retry after some time if the command returnsNil
), and remove the lock just usingDEL
.
OK
时,该客户端得到锁Nil
时,客户端未得到锁,须要过一段时间再重试命令尝试获取锁DEL
删除命令可用来释放锁The lock will be auto-released after the expire time is reached.
当达到失效时间时,锁自动释放。github
It is possible to make this system more robust modifying the unlock schema as follows:redis
- Instead of setting a fixed string, set a non-guessable large random string, called token.
- Instead of releasing the lock with DEL, send a script that only removes the key if the value matches.
This avoids that a client will try to release the lock after the expire time deleting the key created by another client that acquired the lock later.dom
更加健壮的释放锁的方式:分布式
这样能够防止某个客户端在超过失效时间后尝试释放锁,直接使用 DEL 可能会删除掉别的客户端添加的锁。ui
下面是释放锁脚本的例子:this
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
The script should be called with
EVAL ...script... 1 resource-name token-value
执行 EVAL ...script... 1 resource-name token-value
命令释放锁。spa
以上是官方文档中的内容,阅读到这里能够发现一个问题:线程
针对这个问题,能够看下 Redisson 是如何解决的。
官方文档:
https://github.com/redisson/r...
经过如下方式,能够得到一个 key 为 myLock
的 RLock
对象:
Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("myLock");
获取锁和释放锁:
lock.lock(); // 获取锁 try { ... } finally { lock.unlock(); // 在 finally 中释放锁 }
RLock
提供了如下多种获取锁的方法:
void lock()
void lock(long leaseTime, TimeUnit unit)
void lockInterruptibly()
void lockInterruptibly(long leaseTime, TimeUnit unit)
boolean tryLock()
boolean tryLock(long time, TimeUnit unit)
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)
RLock
实现了 java.util.concurrent.locks.Lock
接口,因此 RLock
是符合 Java 中的 Lock
接口规范的。以上的方法中,这四个方法是来源于 Java 中的 Lock
接口:
void lock()
获取锁,若是锁不可用,则当前线程一直等待,直到得到到锁void lockInterruptibly()
和 lock()
方法相似,区别是 lockInterruptibly()
方法在等待的过程当中能够被 interrupt 打断boolean tryLock()
获取锁,不等待,当即返回一个 boolean 类型的值表示是否获取成功boolean tryLock(long time, TimeUnit unit)
获取锁,若是锁不可用,则等待一段时间,等待的最长时间由 long time
和 TimeUnit unit
两个参数指定,若是超过期间未得到锁则返回 false,获取成功返回 true除了以上四个方法外,还有三个方法不是来源于 Java 中的 Lock
接口,而是 RLock
中的方法。这三个方法和上面四个方法有一个最大的区别就是多了一个 long leaseTime
参数。leaseTime
指的就是 Redis 中的 key 的失效时间。经过这三个方法获取到的锁,若是达到 leaseTime
锁还未释放,那么这个锁会自动失效。
回到上面的问题:若是设置了失效时间,当任务未完成且达到失效时间时,锁会被自动释放;若是不设置失效时间,忽然 crash 了,锁又会永远得不到释放。Redisson 是怎么解决这个问题的呢?
If Redisson instance which acquired lock crashes then such lock could hang forever in acquired state. To avoid this Redisson maintains lock watchdog, it prolongs lock expiration while lock holder Redisson instance is alive. By default lock watchdog timeout is 30 seconds and can be changed through
Config.lockWatchdogTimeout
setting.
为了防止 Redisson 实例 crash 致使锁永远不会被释放,针对未指定 leaseTime
的四个方法,Redisson 为锁维护了看门狗(watchdog)。看门狗每隔一段时间去延长一下锁的失效时间。锁的默认失效时间是 30 秒,可经过 Config.lockWatchdogTimeout
修改。延长失效时间的任务的执行频率也是由该配置项决定,是锁的失效时间的 1/3,即默认每隔 10 秒执行一次。
若是 Redisson 实例 crash 了,看门狗也会跟着 crash,那么达到失效时间这个 key 会被 Redis 自动清除,锁也就被释放了,不会出现锁永久被占用的状况。