redisson redlock(基于redisson框架和redis集群使用分布式锁)
一、關于分布式鎖的兩篇文章
文章1
文章2
二、redis分布式鎖存在的問題
redis實現分布式鎖有很多種方案,比較完善的方案應該是用setNx + lua進行實現。簡單實現如下:
- java代碼-加鎖,相當于
- lua腳本-解鎖,原子性操作
- 解釋
- NX:僅在不存在 key 的時候才能被執行成功;
- PX:失效時間,傳入 30000,就是 30s 后自動釋放鎖;
- unique_value:就是隨機值,可以是線程號之類的。主要是為了更安全的釋放鎖,釋放鎖的時候使用腳本告訴 Redis: 只有 key 存在并且存儲的值和我指定的值一樣才能刪除成功。
- 為什么要設置隨機值:主要是為了防止鎖被其他客戶端刪除。
注意:
注意,不能一味認為鎖過期的時間應該比key的expire要長,因為接下來要介紹的redisson框架中有續期機制(看門狗機制),該機制的核心就是:如果線程仍舊沒有執行完,那么redisson會自動給redis中的目標key延長超時時間
在分布式系統中,為了避免單點故障,提高可靠性,redis都會采用主從架構,當主節點掛了后,從節點會作為主繼續提供服務。該種方案能夠滿足大多數的業務場景,但是對于要求強一致性的場景如交易,該種方案還是有漏洞的,原因如下:
因此,上述基于redis實現的分布式鎖只是滿足了AP,并沒有滿足C。
三、redlock
單實例肯定不是很可靠吧?加鎖成功之后,結果 Redis 服務宕機了,就涼了。這時候會提出來將 Redis 主從部署。即使是主從,也是存在巧合的!
- 主從結構中存在明顯的競態:
有時候程序就是這么巧,比如說正好一個節點掛掉的時候,多個客戶端同時取到了鎖。如果你可以接受這種小概率錯誤,那用這個基于復制的方案就完全沒有問題。
- 如果使用集群呢
我們知道對集群進行加鎖的時候,其實是通過 CRC16 的 hash 函數來對 key 進行取模,將結果路由到預先分配過 slot 的相應節點上。發現其實還是發到單個節點上的!
- 正是因為上述redis分布式鎖存在的一致性問題,redis作者提出了一個更加高級的基于redis實現的分布式鎖——RedLock。原文可參考 Distributed locks with Redis,也可以參考這篇文章。
RedLock是基于redis實現的分布式鎖,它能夠保證以下特性:
- 互斥性:在任何時候,只能有一個客戶端能夠持有鎖;
- 避免死鎖:當客戶端拿到鎖后,即使發生了網絡分區或者客戶端宕機,也不會發生死鎖;(利用key的存活時間)
- 容錯性:只要多數節點的redis實例正常運行,就能夠對外提供服務,加鎖或者釋放鎖;
而非redLock是無法滿足互斥性的,上面已經闡述過了原因。
假設有N個redis的master節點,這些節點是相互獨立的(不需要主從或者其他協調的系統)。N推薦為奇數。具體算法如下:
- 注意:為什么N推薦為奇數呢?
-
原因1:本著最大容錯的情況下,占用服務資源最少的原則,2N+1和2N+2的容災能力是一樣的,所以采用2N+1;比如,5臺服務器允許2臺宕機,容錯性為2,6臺服務器也只能允許2臺宕機,容錯性也是2,因為要求超過半數節點存活才OK。
-
原因2:假設有6個redis節點,client1和client2同時向redis實例獲取同一個鎖資源,那么可能發生的結果是——client1獲得了3把鎖,client2獲得了3把鎖,由于都沒有超過半數,那么client1和client2獲取鎖都失敗,對于奇數節點是不會存在這個問題。
參考文章
-
當客戶端無法獲取到鎖時,應該隨機延時后進行重試,防止多個客戶端在同一時間搶奪同一資源的鎖(會導致腦裂,最終都不能獲取到鎖)。客戶端獲得超過半數節點的鎖花費的時間越短,那么腦裂的概率就越低。所以,理想的情況下,客戶端最好能夠同時(并發)向所有redis發出set命令。
-
當客戶端從多數節點獲取鎖失敗時,應該盡快釋放已經成功獲取的鎖,這樣其他客戶端不需要等待鎖過期后再獲取。(如果存在網絡分區,客戶端已經無法和redis進行通信,那么此時只能等待鎖過期后自動釋放)
不明白為什么會發生腦裂???
向所有redis實例發送釋放鎖命令即可,不需要關心redis實例有沒有成功上鎖。
redisson在加鎖的時候,key=lockName, value=uuid + threadID,采用set結構存儲,并包含了上鎖的次數(支持可重入);解鎖的時候通過hexists判斷key和value是否存在,存在則解鎖;這里不會出現誤解鎖
-
如何提升分布式鎖的性能?以每分鐘執行多少次acquire/release操作作為性能指標,一方面通過增加redis實例可用降低響應延遲,另一方面,使用非阻塞模型,一次發送所有的命令,然后異步讀取響應結果,這里假設客戶端和redis之間的RTT差不多。
-
如果redis沒用使用備份,redis重啟后,那么會丟失鎖,導致多個客戶端都能獲取到鎖。通過AOF持久化可以緩解這個問題。redis key過期是unix時間戳,即便是redis重啟,那么時間依然是前進的。但是,如果是斷電呢?redis在啟動后,可能就會丟失這個key(在寫入或者還未寫入磁盤時斷電了,取決于fsync的配置),如果采用fsync=always,那么會極大影響性能。如何解決這個問題呢?可以讓redis節點重啟后,在一個TTL時間段內,對客戶端不可用即可。
四、redlock已經在最新本的redisson中被棄用了
看著 RedLock 好像是解決問題了:
那實際解決問題了么?推薦大家閱讀兩篇文章:
-
Martin Kleppmann:How to do distributed locking
-
Salvatore(Redis 作者):Is Redlock safe?
最終,兩方各持己見,沒有得出結論。具體的源碼分析棄用原因,可以參考這篇文章
五、結論
Redisson RedLock 是基于聯鎖 MultiLock 實現的,但是使用過程中需要自己判斷 key 落在哪個節點上,對使用者不是很友好。Redisson RedLock 已經被棄用,直接使用普通的加鎖即可,會基于 wait 機制將鎖同步到從節點,但是也并不能保證一致性。僅僅是最大限度的保證一致性。
基于MultiLock 實現的分布式鎖
總結
以上是生活随笔為你收集整理的redisson redlock(基于redisson框架和redis集群使用分布式锁)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互联网日报 | 1月22日 星期五 |
- 下一篇: 你的实力就等于别人对待你的态度