Redis如何實現分布式鎖
Redis實現分布式鎖
分布式鎖是用于分布式環(huán)境下并發(fā)控制的一種機制,用于控制某個資源在同一時刻只能被一個應用所使用。
如下圖所示:

Redis 本身可以被多個客戶端共享訪問,正好就是一個共享存儲系統(tǒng),可以用來保存分布式鎖,而且 Redis 的讀寫性能高,可以應對高并發(fā)的鎖操作場景。
Redis 的 SET 命令有個 NX 參數可以實現「key不存在才插入」,所以可以用它來實現分布式鎖:
- 如果 key 不存在,則顯示插入成功,可以用來表示加鎖成功;
- 如果 key 存在,則會顯示插入失敗,可以用來表示加鎖失敗。
基于 Redis 節(jié)點實現分布式鎖時,對于加鎖操作,我們需要滿足三個條件。
- 加鎖包括了讀取鎖變量、檢查鎖變量值和設置鎖變量值三個操作,但需要以原子操作的方式完成,所以,我們使用 SET 命令帶上 NX 選項來實現加鎖;
- 鎖變量需要設置過期時間,以免客戶端拿到鎖后發(fā)生異常,導致鎖一直無法釋放,所以,我們在 SET 命令執(zhí)行時加上 EX/PX 選項,設置其過期時間;
- 鎖變量的值需要能區(qū)分來自不同客戶端的加鎖操作,以免在釋放鎖時,出現誤釋放操作,所以,我們使用 SET 命令設置鎖變量值時,每個客戶端設置的值是一個唯一值,用于標識客戶端;
滿足這三個條件的分布式命令
如下:
SET lock_key unique_value NX PX 10000
- lock_key 就是 key 鍵;
- unique_value 是客戶端生成的唯一的標識,區(qū)分來自不同客戶端的鎖操作;
- NX 代表只在 lock_key 不存在時,才對 lock_key 進行設置操作;
- PX 10000 表示設置 lock_key 的過期時間為 10s,這是為了避免客戶端發(fā)生異常而無法釋放鎖。
而解鎖的過程就是將 lock_key 鍵刪除(del lock_key),但不能亂刪,要保證執(zhí)行操作的客戶端就是加鎖的客戶端。
所以,解鎖的時候,我們要先判斷鎖的 unique_value 是否為加鎖客戶端,是的話,才將 lock_key 鍵刪除。
可以看到,解鎖是有兩個操作,這時就需要 Lua 腳本來保證解鎖的原子性,因為 Redis 在執(zhí)行 Lua 腳本時,可以以原子性的方式執(zhí)行,保證了鎖釋放操作的原子性。
// 釋放鎖時,先比較 unique_value 是否相等,避免鎖的誤釋放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end這樣一來,就通過使用 SET 命令和 Lua 腳本在 Redis 單節(jié)點上完成了分布式鎖的加鎖和解鎖。
基于Redis 實現分布式鎖有什么優(yōu)缺點?
Redis 實現分布式鎖的 優(yōu)點:
- 性能高效(這是選擇緩存實現分布式鎖最核心的出發(fā)點)。
- 實現方便。很多研發(fā)工程師選擇使用 Redis 來實現分布式鎖,很大成分上是因為 Redis 提供了 setnx 方法,實現分布式鎖很方便。
- 避免單點故障(因為 Redis 是跨集群部署的,自然就避免了單點故障)。
Redis 實現分布式鎖的 缺點:
- 超時時間不好設置。如果鎖的超時時間設置過長,會影響性能,如果設置的超時時間過短會保護不到共享資源。比如在有些場景中,一個線程 A 獲取到了鎖之后,由于業(yè)務代碼執(zhí)行時間可能比較長,導致超過了鎖的超時時間,自動失效,注意 A 線程沒執(zhí)行完,后續(xù)線程 B 又意外的持有了鎖,意味著可以操作共享資源,那么兩個線程之間的共享資源就沒辦法進行保護了。
- 那么如何合理設置超時時間呢? 我們可以基于續(xù)約的方式設置超時時間:先給鎖設置一個超時時間,然后啟動一個守護線程,讓守護線程在一段時間后,重新設置這個鎖的超時時間。實現方式就是:寫一個守護線程,然后去判斷鎖的情況,當鎖快失效的時候,再次進行續(xù)約加鎖,當主線程執(zhí)行完成后,銷毀續(xù)約鎖即可,不過這種方式實現起來相對復雜。
- Redis 主從復制模式中的數據是異步復制的,這樣導致分布式鎖的不可靠性。如果在 Redis 主節(jié)點獲取到鎖后,在沒有同步到其他節(jié)點時,Redis 主節(jié)點宕機了,此時新的 Redis 主節(jié)點依然可以獲取鎖,所以多個應用服務就可以同時獲取到鎖。
Redis如何解決集群情況下分布式鎖的可靠性?
為了保證集群環(huán)境下分布式鎖的可靠性,Redis 官方已經設計了一個分布式鎖算法 Redlock(紅鎖)。
它是基于多個 Redis 節(jié)點的分布式鎖,即使有節(jié)點發(fā)生了故障,鎖變量仍然是存在的,客戶端還是可以完成鎖操作。
官方推薦是至少部署 5 個 Redis 節(jié)點,而且都是主節(jié)點,它們之間沒有任何關系,都是一個個孤立的節(jié)點。
Redlock 算法的基本思路,是讓客戶端和多個獨立的 Redis 節(jié)點依次請求申請加鎖,如果客戶端能夠和半數以上的節(jié)點成功地完成加鎖操作,那么我們就認為,客戶端成功地獲得分布式鎖,否則加鎖失敗。
這樣一來,即使有某個 Redis 節(jié)點發(fā)生故障,因為鎖的數據在其他節(jié)點上也有保存,所以客戶端仍然可以正常地進行鎖操作,鎖的數據也不會丟失。
Redlock算法加鎖三個過程
第一步是,客戶端獲取當前時間(t1)。
第二步是,客戶端按順序依次向 N 個 Redis 節(jié)點執(zhí)行加鎖操作:
- 加鎖操作使用 SET 命令,帶上 NX,EX/PX 選項,以及帶上客戶端的唯一標識。
- 如果某個 Redis 節(jié)點發(fā)生故障了,為了保證在這種情況下,Redlock 算法能夠繼續(xù)運行,我們需要給「加鎖操作」設置一個超時時間(不是對「鎖」設置超時時間,而是對「加鎖操作」設置超時時間),加鎖操作的超時時間需要遠遠地小于鎖的過期時間,一般也就是設置為幾十毫秒。
第三步是,一旦客戶端從超過半數(大于等于 N/2+1)的 Redis 節(jié)點上成功獲取到了鎖,就再次獲取當前時間(t2),然后計算計算整個加鎖過程的總耗時(t2-t1)。如果 t2-t1 < 鎖的過期時間,此時,認為客戶端加鎖成功,否則認為加鎖失敗。
加鎖成功要同時滿足兩個條件
(簡述:如果有超過半數的 Redis 節(jié)點成功的獲取到了鎖,并且總耗時沒有超過鎖的有效時間,那么就是加鎖成功):
- 條件一:客戶端從超過半數(大于等于 N/2+1)的 Redis 節(jié)點上成功獲取到了鎖;
- 條件二:客戶端從大多數節(jié)點獲取鎖的總耗時(t2-t1)小于鎖設置的過期時間。
加鎖成功后,客戶端需要重新計算這把鎖的有效時間,計算的結果是「鎖最初設置的過期時間」減去「客戶端從大多數節(jié)點獲取鎖的總耗時(t2-t1)」。
如果計算的結果已經來不及完成共享數據的操作了,我們可以釋放鎖,以免出現還沒完成數據操作,鎖就過期了的情況。
加鎖失敗后,客戶端向所有 Redis 節(jié)點發(fā)起釋放鎖的操作,釋放鎖的操作和在單節(jié)點上釋放鎖的操作一樣,只要執(zhí)行釋放鎖的 Lua 腳本就可以了。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
RedisTemplate中boundHashOps的使用小結
redisTemplate.boundHashOps(key)?是 RedisTemplate 類的一個方法,本文主要介紹了RedisTemplate中boundHashOps的使用小結,具有一定的參考價值,感興趣的可以了解一下2024-04-04
RediSearch加RedisJSON大于Elasticsearch的搜索存儲引擎
這篇文章主要為大家介紹了RediSearch加RedisJSON大于Elasticsearch的王炸使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07
詳解redis腳本命令執(zhí)行問題(redis.call)
這篇文章主要介紹了redis腳本命令執(zhí)行問題(redis.call),分別介紹了redis-cli命令行中執(zhí)行及l(fā)inux命令行中執(zhí)行問題,本文給大家介紹的非常詳細,需要的朋友參考下吧2022-03-03
Redis02 使用Redis數據庫(String類型)全面解析
這篇文章主要介紹了Redis02 使用Redis數據庫(String類型)全面解析的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07

