基於redis的分佈式鎖


基於redis的分佈式鎖

概述

在之前, 我也使用redis做過分佈式鎖, 當時的做法是這樣的:

  1. setnx: 向 redis中創建一個過期時間為1s的key, 若創建失敗, 則鎖獲取失敗
  2. expire: 獲取鎖成功後, 給鎖增加過期時間
  3. del: 處理後釋放鎖

當時覺得貌似沒什麼問題. 是我太天真了, 今天突然想到, 恩, 有問題.

問題

1.如果在第一步之後, 程序崩了, 沒有給鎖設置過期時間, 導致所有後續操作都無法正常獲取到鎖. 怎麼破?

2.在A成功上鎖後, 因為IO阻塞等原因, 執行時間有點長, 鎖已經過期了, 這時B過來成功上鎖, A在釋放鎖的時候釋放的就是B的鎖.

3.redis突然掛了. 如果redis突然掛了, 怎麼辦? 當然, 可以增加redis節點, 主節點掛了, 從節點立刻補上. 但是, 主節點的數據同步到從節點也是需要時間的吧. 假設一個場景:

  1. A在主節點設置鎖
  2. 主節點還沒有同步數據的時候, 掛了
  3. 從節點接替成為主節點
  4. B在主節點也成功設置了鎖

這個時候, 分佈式鎖就失效了.

解決

那麼有沒有辦法解決上面的問題呢? 我到萬能的谷歌上找了一下, 恩, 真的有.

上面的問題一個一個解決.

問題一

如何避免沒有給鎖設置過期時間的問題?

其實看看就知道了, 問題出在設置key和設置value分成兩條命令執行, 所以導致如果在 setnx命令執行過後, 程序崩潰, expire命令沒有正常執行, 將其合併為一條命令就好啦.

set key value NX PX 5000

其中NX表示存在則不設置, PX表示過期時間.

如此, 至少可以保證不會出現沒有過期時間的鎖了

問題二

如何避免A釋放了B的鎖.

如何避免釋放了其他人的鎖呢? 換個問題, 如何保證這個鎖是你加的呢? so easy, 加鎖的時候, 將value值設置成一個只有我知道的隨機數字, 釋放的時候看看值是不是我的就行了.

如此在釋放的時候需要兩步操作:

  1. 獲取redis鎖的值
  2. 若值是我的, 釋放鎖

當然, 為了保證釋放鎖操作的原子性, 這兩步操作最好也能合併為一步操作. 那redis如何實現值是否相同的判斷呢? Lua腳本.

簡單介紹一下

<code>eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 argv1 argv2
# 看懂了吧, 哈哈
# eval 是redis內置的命令
# 第一個參數是運行的腳本邏輯
# 第二個參數表示後面有幾個key
# 第五個參數開始就是附加參數, 在腳本邏輯中使用的/<code>

所以, 腳本內容如下:

<code>if redis.call("get",KEYS[1]) == ARGV[1] then
   return redis.call("del",KEYS[1])
else
   return 0
end/<code>

如此, 至少可以保證不會出現A釋放了B鎖的情況了

問題三

如何保證在主節點掛掉的時候, 從節點接替後, 不會重複獲得鎖?

官網上提供了一個方法, 從多個redis實例同時獲取鎖. 因為我沒看太明白, 之後看懂了在說吧. 過...

其實, 如果不是處理金錢這種不容出錯的業務, 這種小概率事件個人覺得還是可以容忍的.


總結

最終, 在redis單機下實現的分佈式鎖操作如下:

<code># 獲取分佈式鎖,過期時間可調
set lock_key random_value NX PX 5000
# ...do something
# 釋放分佈式鎖
eval "if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end" 1 lock_key random_value /<code>


分享到:


相關文章: