分佈式鎖注意點及其實現
來源:SegmentFault 思否社區
作者:skyarthur
分佈式鎖注意點
1)互斥性
在任意時刻只有一個客戶端可以獲取鎖
2)防死鎖
假如一個客戶端在持有鎖的時候崩潰了,沒有釋放鎖,那麼別的客戶端無法獲得鎖,則會造成死鎖,所以要保證客戶端一定會釋放鎖。Redis 中我們可以設置鎖的過期時間來保證不會發生死鎖。
3)持鎖人解鎖
解鈴還須繫鈴人,加鎖和解鎖必須是同一個客戶端,客戶端 A 的線程加的鎖必須是客戶端 A 的線程來解鎖,客戶端不能解開別的客戶端的鎖。
4)可重入
當一個客戶端獲取對象鎖之後,這個客戶端可以再次獲取這個對象上的鎖。
redis 分佈式鎖
實現
Redis 鎖主要利用 Redis 的 setnx 命令。
-
加鎖命令:SETNX key value,當鍵不存在時,對鍵進行設置操作並返回成功,否則返回失敗。KEY 是鎖的唯一標識,一般按業務來決定命名。
-
解鎖命令:DEL key,通過刪除鍵值對釋放鎖,以便其他線程可以通過 SETNX 命令來獲取鎖。
-
鎖超時:EXPIRE key timeout, 設置 key 的超時時間,以保證即使鎖沒有被顯式釋放,鎖也可以在一定時間後自動釋放,避免資源被永遠鎖住。
則加鎖解鎖僞代碼如下:
if (setnx(key, 1) == 1){
expire(key, 30)
try {
//TODO 業務邏輯
} finally {
del(key)
}
}
注意點
SETNX 和 EXPIRE 非原子性
如果 SETNX 成功,在設置鎖超時時間後,服務器掛掉、重啓或網絡問題等,導致 EXPIRE 命令沒有執行,鎖沒有設置超時時間變成死鎖。
解決辦法
-
setnx 的時候,value 設置和過期時間設置不是原子,可以用 set 命令,redis 版本 2.6.12 以後
-
lua 腳本,示例如下
if (redis.call('setnx', KEYS[1], ARGV[1]) < 1)
then return 0;
end;
redis.call('expire', KEYS[1], tonumber(ARGV[2]));
return 1;
// 使用實例
EVAL "if (redis.call('setnx',KEYS[1],ARGV[1]) < 1) then return 0; end; redis.call('expire',KEYS[1],tonumber(ARGV[2])); return 1;" 1 key value 100
錯誤解除
如果線程 A 成功獲取到了鎖,並且設置了過期時間 30 秒,但線程 A 執行時間超過了 30 秒,鎖過期自動釋放,此時線程 B 獲取到了鎖;隨後 A 執行完成,線程 A 使用 DEL 命令來釋放鎖,但此時線程 B 加的鎖還沒有執行完成,線程 A 實際釋放的線程 B 加的鎖。
解決辦法
- 釋放鎖的時候,只能釋放自己的,不能釋放別人,所以需要 getValue 然後和自己的 id 比較,一致了才能 delete,這裏也需要是原子操作(其實覺得可以通過重試機制來避免)
超時解鎖導致併發
如果線程 A 成功獲取鎖並設置過期時間 30 秒,但線程 A 執行時間超過了 30 秒,鎖過期自動釋放,此時線程 B 獲取到了鎖,線程 A 和線程 B 併發執行。
A、B 兩個線程發生併發顯然是不被允許的,一般有兩種方式解決該問題:
-
將過期時間設置足夠長,確保代碼邏輯在鎖釋放之前能夠執行完成。
-
爲獲取鎖的線程增加守護線程,爲將要過期但未釋放的鎖增加有效時間。
當線程在持有鎖的情況下再次請求加鎖,如果一個鎖支持一個線程多次加鎖,那麼這個鎖就是可重入的。如果一個不可重入鎖被再次加鎖,由於該鎖已經被持有,再次加鎖會失敗。Redis 可通過對鎖進行重入計數,加鎖時加 1,解鎖時減 1,當計數歸 0 時釋放鎖。
主備切換
爲了保證 Redis 的可用性,一般採用主從方式部署。主從數據同步有異步和同步兩種方式,Redis 將指令記錄在本地內存 buffer 中,然後異步將 buffer 中的指令同步到從節點,從節點一邊執行同步的指令流來達到和主節點一致的狀態,一邊向主節點反饋同步情況。
在包含主從模式的集羣部署方式中,當主節點掛掉時,從節點會取而代之,但客戶端無明顯感知。當客戶端 A 成功加鎖,指令還未同步,此時主節點掛掉,從節點提升爲主節點,新的主節點沒有鎖的數據,當客戶端 B 加鎖時就會成功。
集羣腦裂指因爲網絡問題,導致 Redis master 節點跟 slave 節點和 sentinel 集羣處於不同的網絡分區,因爲 sentinel 集羣無法感知到 master 的存在,所以將 slave 節點提升爲 master 節點,此時存在兩個不同的 master 節點。Redis Cluster 集羣部署方式同理。
當不同的客戶端連接不同的 master 節點時,兩個客戶端可以同時擁有同一把鎖。如下:
Redis 以其高性能著稱,但使用其實現分佈式鎖來解決併發仍存在一些困難。Redis 分佈式鎖只能作爲一種緩解併發的手段,如果要完全解決併發問題,仍需要數據庫的防併發手段。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/EEImynY2XsUj8b_2u2LI2Q