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 自動清除,鎖也就被釋放了,不會出現鎖永久被佔用的狀況。