分佈式鎖的基本概念
分佈式鎖可以理解為"多個人搶同一個東西時,用一把鎖來保證只有一個人能拿到",但這裏的"多個人"不是單台機器上的多個線程,而是多台服務器(分佈式系統)。
例子:電商平台下單,庫存只有1件,同時有10個人在不同地方搶,這時候就需要一把"分佈式鎖",保證只有一個人能成功扣減庫存,避免超賣。
Redis分佈式鎖的實現方式
1. 最基礎的:用 setnx 命令("set if not exists")
原理:往Redis裏存一個key(比如 lock:stock),如果這個key不存在,就存成功(拿到鎖);如果已經存在,就存失敗(沒拿到鎖)。
使用方式:
- 搶鎖:執行
setnx lock:stock 1,成功就是拿到鎖,然後做操作(比如扣庫存) - 釋放鎖:操作完了,用
del lock:stock刪除key,釋放鎖,讓別人能搶
類比:就像廁所隔間,門上的鎖(key)沒人用的時候,你才能鎖上(setnx成功),用完了開鎖(del)。
2. 加過期時間的:set 命令帶參數(推薦)
為什麼需要:如果拿到鎖的服務器突然宕機,沒來得及釋放鎖,這個key就會一直存在,別人永遠拿不到鎖(死鎖)。所以給鎖加個"過期時間",即使宕機,時間到了鎖會自動釋放。
使用方式:直接用 set lock:stock 1 EX 10 NX(EX 10 是過期10秒,NX 是"不存在才設置",相當於 setnx +過期時間一步到位)
- 搶鎖:執行上面的命令,成功就是拿到鎖,操作完手動
del釋放 - 自動釋放:如果服務器宕機,10秒後key自動消失,鎖釋放
3. 更完善的:加唯一標識+Lua腳本釋放
為什麼需要:假設A拿到鎖(過期時間10秒),但A的操作很慢,10秒還沒做完,鎖自動釋放了,這時候B拿到了鎖。這時候A操作完了,執行 del 會把B的鎖刪掉(誤刪)。
解決辦法:
- 存key的時候,value用"唯一標識"(比如UUID),代表"這把鎖是我的"
- 釋放鎖時,先用腳本判斷"這個key的value是不是我的標識",是才刪除,不是就不刪
使用方式:
- 搶鎖:
set lock:stock uuid-xxx EX 10 NX(value是唯一標識) - 釋放鎖:用Lua腳本執行
if redis.call('get', 'lock:stock') == 'uuid-xxx' then return redis.call('del', 'lock:stock') else return 0 end
Redis分佈式鎖可能出現的問題及解決方案
1. 死鎖
問題:拿到鎖的節點宕機,沒釋放鎖,其他節點永遠拿不到鎖。
解決:給鎖加過期時間(set 命令帶 EX 參數),即使節點宕機,過期後鎖自動釋放。
2. 鎖過期但任務未完成
問題:鎖過期時間設短了,任務還在執行,鎖已釋放,其他節點趁機拿到鎖,導致"併發操作"。
解決:
- 預估任務耗時,設置足夠長的過期時間(留冗餘)
- 用"鎖續期"機制(如開個後台線程,任務沒完成時,每隔一段時間延長鎖的過期時間,類似"看門狗")
3. 誤刪他人的鎖
問題:A的鎖過期後,B拿到鎖,A的任務恰好執行完,執行 del 命令誤刪了B的鎖。
解決:
- 加鎖時給
value設唯一標識(如UUID),代表"這把鎖是我的" - 釋放鎖時用Lua腳本判斷:只有
value匹配自己的標識時,才執行del(避免誤刪)
4. Redis集羣"腦裂"問題
問題:Redis主從集羣中,主庫宕機,從庫還沒同步到"鎖已被持有"的信息,就升級為新主庫,導致新節點能再次加鎖,出現"雙鎖"。
解決:
- 用
RedLock算法(向多個獨立的Redis節點加鎖,超過半數節點加鎖成功才算拿到鎖,降低單節點同步問題的影響) - 業務層面容忍一定概率的衝突(中小業務可簡化,大業務再用複雜方案)
總結
實現方式對比
- 最基礎:
setnx+del(但有死鎖風險) - 推薦基礎版:
set key value EX 時間 NX(加過期時間,防死鎖) - 更安全版:加唯一標識+Lua腳本釋放(防誤刪)
核心問題與解決
- 死鎖:加過期時間
- 鎖過期任務未完成:合理設置過期時間或鎖續期
- 誤刪鎖:唯一標識+Lua腳本
- 集羣腦裂:視業務複雜度選擇RedLock
使用場景
需要用鎖的場景:多節點競爭同一資源,必須保證操作的"原子性"。