概述
在之前, 我也使用redis做過分佈式鎖, 當時的做法是這樣的:
setnx: 向 redis中創建一個過期時間為1s的key, 若創建失敗, 則鎖獲取失敗expire: 獲取鎖成功後, 給鎖增加過期時間del: 處理後釋放鎖當時覺得貌似沒什麼問題. 是我太天真了, 今天突然想到, 恩, 有問題.
問題
1.如果在第一步之後, 程序崩了, 沒有給鎖設置過期時間, 導致所有後續操作都無法正常獲取到鎖. 怎麼破?
2.在A成功上鎖後, 因為IO阻塞等原因, 執行時間有點長, 鎖已經過期了, 這時B過來成功上鎖, A在釋放鎖的時候釋放的就是B的鎖.
3.redis突然掛了. 如果redis突然掛了, 怎麼辦? 當然, 可以增加redis節點, 主節點掛了, 從節點立刻補上. 但是, 主節點的數據同步到從節點也是需要時間的吧. 假設一個場景:
A在主節點設置鎖主節點還沒有同步數據的時候, 掛了從節點接替成為主節點B在主節點也成功設置了鎖這個時候, 分佈式鎖就失效了.
解決
那麼有沒有辦法解決上面的問題呢? 我到萬能的谷歌上找了一下, 恩, 真的有.
上面的問題一個一個解決.
問題一
如何避免沒有給鎖設置過期時間的問題?
其實看看就知道了, 問題出在設置key和設置value分成兩條命令執行, 所以導致如果在 setnx命令執行過後, 程序崩潰, expire命令沒有正常執行, 將其合併為一條命令就好啦.
set key value NX PX 5000
其中NX表示存在則不設置, PX表示過期時間.
如此, 至少可以保證不會出現沒有過期時間的鎖了
問題二
如何避免A釋放了B的鎖.
如何避免釋放了其他人的鎖呢? 換個問題, 如何保證這個鎖是你加的呢? so easy, 加鎖的時候, 將value值設置成一個只有我知道的隨機數字, 釋放的時候看看值是不是我的就行了.
如此在釋放的時候需要兩步操作:
獲取redis鎖的值若值是我的, 釋放鎖當然, 為了保證釋放鎖操作的原子性, 這兩步操作最好也能合併為一步操作. 那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>