Redis 分散式鎖

Fang思楠發表於2024-06-21

Redis 分散式鎖

分散式鎖的演變

  1. 本地鎖(單機用)
  2. 利用redis進行分散式鎖 使用 set
  3. 防止死鎖 加過期時間 使用 setnx
  4. 防止A請求未執行完 鎖過期刪除 B請求加鎖後 A完成後誤刪該鎖 使用 Hash結構, 規定每個請求只能刪除自己的鎖
  5. 保證併發安全,申請鎖和加過期時間需要 原子性,用 lua指令碼 加鎖或解鎖
  6. 考慮到 重入性 (每個請求只拿到一個鎖後,可以多函式或執行緒共用) 使用 Hash結構進行加減(hincrby) 操作
  7. 為了保證業務執行過長,鎖不會過期。需要對鎖進行 續期

分散式鎖的特性

  1. 獨佔性
  2. 高可用
  3. 防死鎖
  4. 不亂搶
  5. 可重入

1. setNX

  • 注意死鎖
  • 注意lock過期時間和業務執行時間
  • 加鎖不成功時,等待獲取機制 ( 注意停幾毫秒 )
  • 一般情況下完全夠用

2. 考慮look重入性

  • 當一個鎖被建立出來之後再同一個請求中不需要再額外申請其他鎖,一個鎖可以被重複使用,所以使用到 Hash資料結構的鎖
  • 如果業務上需要應用到多個函式的鎖的情況下,申請一個鎖之後固定當前請求的uuid+當前執行緒id。入一個函式(或者執行緒)向Hash的uuid +1
  • 直到請求結束後,檢查執行緒鎖是否歸0 如果是釋放該鎖,如果不是說明其他執行緒未能執行完畢。
  • 考慮可重入性的遞減。 加鎖幾次就要減幾次,到0後刪除
鎖的操作保證原子性應對高併發
  • 當一個鎖(Hash結構) 建立鎖和增加過期時間,兩步需要lua指令碼進行執行保證原子性。
// lua 指令碼編寫 
// 加鎖操作  如果鎖不存在或者當前請求鎖的uuid欄位存在 則進行加一 (重入性)
KEYS[1]  //  -- 分散式鎖的key
ARGV[1]  //  -- 鎖的唯一標識,通常是執行緒ID或呼叫者標識
ARGV[2]  //  -- 鎖的過期時間,單位為毫秒

if redis.call('exists', KEYS[1]) == 0 //鎖不存在
or redis.call('hexists', KEYS[1], ARGV[1]) == 1 // 當前請求有鎖 進入函式(或新開執行緒)無需額外申請鎖
then
  redis.call('hincrby',KEYS[1], ARGV[1], 1)
  redis.call('expire',KEYS[1], ARGV[2])
  return true
else
  return false
end

//解鎖操作
  if redis.call("hexists",KEYS[1], ARGV[1]) == 0 then // 無鎖  無需解鎖
      return false
  else if redis.call("hincrby",KEYS[1], ARGV[1], -1) == 0 then  // 鎖存在且是自己請求的鎖 進行減一操作,如果減為0 則解鎖(刪key)
      retrun redis.call("DEL",KEYS[1])
  else
      return 0
  • 當lock 建立時,需要同步啟動一個定時續期任務,鎖存在並過該定時時間進行續期,防止業務未完直接釋放鎖。
  • 當主執行緒執行完業務流程 並釋放鎖之後,續期機制同時結束。

相關文章