前言
RedLock 紅鎖,是分散式鎖中必須要了解的一個概念。
所以本文會先介紹什麼是 RedLock,當大家對 RedLock 有一個基本的瞭解。然後再看 Redisson 中是如何實現 RedLock 的。
在文章開頭先說明 Redisson RedLock 建議不要使用!!!
在文章開頭先說明 Redisson RedLock 建議不要使用!!!
在文章開頭先說明 Redisson RedLock 建議不要使用!!!
重要的事情重複三遍!
什麼是 RedLock?
RedLock,這塊可以從網上搜到很多資料,本文也簡單介紹下,當做掃盲。
單例項加鎖
SET resource_name my_random_value NX PX 30000
對於單例項 Redis 只需要使用這個命令即可。
- NX:僅在不存在 key 的時候才能被執行成功;
- PX:失效時間,傳入 30000,就是 30s 後自動釋放鎖;
- my_random_value:就是隨機值,可以是執行緒號之類的。主要是為了更安全的釋放鎖,釋放鎖的時候使用指令碼告訴 Redis: 只有 key 存在並且儲存的值和我指定的值一樣才能刪除成功。
可以通過以下 Lua 指令碼實現鎖釋放:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
為什麼要設定隨機值?
主要是為了防止鎖被其他客戶端刪除。有這麼一種情況:
- 客戶端 A 獲得了鎖,還沒有執行結束,但是鎖超時自動釋放了;
- 客戶端 B 此時過來,是可以獲得鎖的,加鎖成功;
- 此時,客戶端 A 執行結束了,要去釋放鎖,如果不對比隨機值,就會把客戶端 B 的鎖給釋放了。
當然前面看過 Redisson 的處理,這個 my_random_value 存放的是 UUID:ThreadId
組合成的一個類似 931573de-903e-42fd-baa7-428ebb7eda80:1
的字串。
當鎖遇到故障轉移
單例項肯定不是很可靠吧?加鎖成功之後,結果 Redis 服務當機了,這不就涼涼~
這時候會提出來將 Redis 主從部署。即使是主從,也是存在巧合的!
主從結構中存在明顯的競態:
- 客戶端 A 從 master 獲取到鎖
- 在 master 將鎖同步到 slave 之前,master 宕掉了。
- slave 節點被晉級為 master 節點
- 客戶端 B 取得了同一個資源被客戶端 A 已經獲取到的另外一個鎖。安全失效!
有時候程式就是這麼巧,比如說正好一個節點掛掉的時候,多個客戶端同時取到了鎖。如果你可以接受這種小概率錯誤,那用這個基於複製的方案就完全沒有問題。
那我使用叢集呢?
如果還記得前面的內容,應該是知道對叢集進行加鎖的時候,其實是通過 CRC16 的 hash 函式來對 key 進行取模,將結果路由到預先分配過 slot 的相應節點上。
發現其實還是發到單個節點上的!
RedLock 概念
這時候 Redis 作者提出了 RedLock 的概念
總結一下就是對叢集的每個節點進行加鎖,如果大多數(N/2+1)加鎖成功了,則認為獲取鎖成功。
RedLock 的問題
看著 RedLock 好像是解決問題了:
- 客戶端 A 鎖住了叢集的大多數(一半以上);
- 客戶端 B 也要鎖住大多數;
- 這裡肯定會衝突,所以 客戶端 B 加鎖失敗。
那實際解決問題了麼?
推薦大家閱讀兩篇文章:
-
Martin Kleppmann:How to do distributed locking
-
Salvatore(Redis 作者):Is Redlock safe?
最終,兩方各持己見,沒有得出結論。
鑑於本文主要是分析 Redisson 的 RedLock,就不做額外贅述,感興趣的小夥伴可以自己閱讀。
Redisson 中 RedLock 原始碼
這裡會簡要分析一下 Redisson 中 RedLock 的原始碼,然後會介紹為什麼文章開頭不建議大家使用 Redisson 的 RedLock。
使用方式
乍一看,感覺和聯鎖 MultiLock 的使用方式很像啊!
實際上就是很像,RedissonRedLock 完全是 RedissonMultiLock 的子類嘛!
只不過是重寫 failedLocksLimit
方法。
在 MultiLock 中,要所有的鎖都鎖成功才可以。
在 RedLock 中,要一半以上的鎖成功。
剩餘部分原始碼都和 MultiLock 一樣,就不在重複描述了。
Redisson 中 RedLock 的問題
1、加鎖 key 的問題
閱讀原始碼之前,有一個很大的疑問,我加鎖 lock1、lock2、lock3,但是 RedissonRedLock 是如何保證這三個 key 是在歸屬於 Redis 叢集中不同的 master 呢?
因為按照 RedLock 的理論,是需要在半數以上的 master 節點加鎖成功。
閱讀完原始碼之後,發現 RedissonRedLock 完全是 RedissonMultiLock 的子類,只是重寫了 failedLocksLimit
方法,保證半數以上加鎖成功即可。
所以這三個 key,是需要使用者來保證分散在不同的節點上的。
在 Redisson 的 issues 也有同樣的小夥伴提出這個問題,相關開發者給出的回覆是使用者來保證 key 分散在不同的 master 上。
更有小夥伴提出使用 5 個客戶端。
那我使用 5 個單節點的客戶端,然後再使用紅鎖,聽著好像是可以的,並且 RedissonRedLock 可以這樣使用。
但是那和 Redis 叢集還有啥關係啊!
所以依然沒有解決我的問題,還是需要使用者自己來“手工定位鎖”。
手工定位鎖,這個…… 我考慮了下,還是不用 RedLock 吧!
當然 DarrenJiang1990 同學應該是懷著打破砂鍋問到底的心情,又來了一篇 issue。
https://github.com/redisson/redisson/issues/2437
意思就是:不要關閉我的 issues,在 #2436 中說可以“手工定位鎖”,但是我要怎麼手工定位鎖。
後來這個 issue 在 10 月才回復。
2、RedissonRedLock 被棄用
是的,沒有看錯,現在 RedissonRedLock 已經被啟用了。
如果是看的英文文件,就會發現:
而中文文件,應該是沒有及時更新。
來看看更新記錄:
再找一找 issue:
Redisson 的開發者認為 Redis 的紅鎖也存在爭議(前文介紹的那個爭議),但是為了保證可用性,RLock 物件執行的每個 Redis 命令執行都通過 Redis 3.0 中引入的 WAIT 命令進行同步。
WAIT 命令會阻塞當前客戶端,直到所有以前的寫命令都成功的傳輸並被指定數量的副本確認。如果達到以毫秒為單位指定的超時,則即使尚未達到指定數量的副本,該命令也會返回。
WAIT 命令同步複製也並不能保證強一致性,不過在主節點當機之後,只不過會盡可能的選擇最佳的副本(slaves)
原始碼在這一部分。
看原始碼,同時傳送了一個 WAIT 1 1000
到 Redis。
結論
Redisson RedLock 是基於聯鎖 MultiLock 實現的,但是使用過程中需要自己判斷 key 落在哪個節點上,對使用者不是很友好。
Redisson RedLock 已經被棄用,直接使用普通的加鎖即可,會基於 wait 機制將鎖同步到從節點,但是也並不能保證一致性。僅僅是最大限度的保證一致性。