Redisson 分散式鎖原始碼 09:RedLock 紅鎖的故事

程式設計師小航發表於2021-07-12

前言

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

為什麼要設定隨機值?

主要是為了防止鎖被其他客戶端刪除。有這麼一種情況:

  1. 客戶端 A 獲得了鎖,還沒有執行結束,但是鎖超時自動釋放了;
  2. 客戶端 B 此時過來,是可以獲得鎖的,加鎖成功;
  3. 此時,客戶端 A 執行結束了,要去釋放鎖,如果不對比隨機值,就會把客戶端 B 的鎖給釋放了。

當然前面看過 Redisson 的處理,這個 my_random_value 存放的是 UUID:ThreadId 組合成的一個類似 931573de-903e-42fd-baa7-428ebb7eda80:1 的字串。

當鎖遇到故障轉移

單例項肯定不是很可靠吧?加鎖成功之後,結果 Redis 服務當機了,這不就涼涼~

這時候會提出來將 Redis 主從部署。即使是主從,也是存在巧合的!

主從結構中存在明顯的競態:

  1. 客戶端 A 從 master 獲取到鎖
  2. 在 master 將鎖同步到 slave 之前,master 宕掉了。
  3. slave 節點被晉級為 master 節點
  4. 客戶端 B 取得了同一個資源被客戶端 A 已經獲取到的另外一個鎖。安全失效!

有時候程式就是這麼巧,比如說正好一個節點掛掉的時候,多個客戶端同時取到了鎖。如果你可以接受這種小概率錯誤,那用這個基於複製的方案就完全沒有問題。

那我使用叢集呢?

如果還記得前面的內容,應該是知道對叢集進行加鎖的時候,其實是通過 CRC16 的 hash 函式來對 key 進行取模,將結果路由到預先分配過 slot 的相應節點上。

發現其實還是發到單個節點上的

RedLock 概念

這時候 Redis 作者提出了 RedLock 的概念

總結一下就是對叢集的每個節點進行加鎖,如果大多數(N/2+1)加鎖成功了,則認為獲取鎖成功。

RedLock 的問題

看著 RedLock 好像是解決問題了:

  1. 客戶端 A 鎖住了叢集的大多數(一半以上);
  2. 客戶端 B 也要鎖住大多數;
  3. 這裡肯定會衝突,所以 客戶端 B 加鎖失敗。

那實際解決問題了麼?

推薦大家閱讀兩篇文章:

最終,兩方各持己見,沒有得出結論。

鑑於本文主要是分析 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,是需要使用者來保證分散在不同的節點上的。

https://github.com/redisson/redisson/issues/2436

在 Redisson 的 issues 也有同樣的小夥伴提出這個問題,相關開發者給出的回覆是使用者來保證 key 分散在不同的 master 上。

https://github.com/redisson/redisson/issues/2127

更有小夥伴提出使用 5 個客戶端。

那我使用 5 個單節點的客戶端,然後再使用紅鎖,聽著好像是可以的,並且 RedissonRedLock 可以這樣使用。

但是那和 Redis 叢集還有啥關係啊!

所以依然沒有解決我的問題,還是需要使用者自己來“手工定位鎖”。

手工定位鎖,這個…… 我考慮了下,還是不用 RedLock 吧!

當然 DarrenJiang1990 同學應該是懷著打破砂鍋問到底的心情,又來了一篇 issue。

https://github.com/redisson/redisson/issues/2437

意思就是:不要關閉我的 issues,在 #2436 中說可以“手工定位鎖”,但是我要怎麼手工定位鎖。

後來這個 issue 在 10 月才回復。

2、RedissonRedLock 被棄用

是的,沒有看錯,現在 RedissonRedLock 已經被啟用了。

如果是看的英文文件,就會發現:

而中文文件,應該是沒有及時更新。

來看看更新記錄:

再找一找 issue:

https://github.com/redisson/redisson/issues/2669

Redisson 的開發者認為 Redis 的紅鎖也存在爭議(前文介紹的那個爭議),但是為了保證可用性,RLock 物件執行的每個 Redis 命令執行都通過 Redis 3.0 中引入的 WAIT 命令進行同步。

WAIT 命令會阻塞當前客戶端,直到所有以前的寫命令都成功的傳輸並被指定數量的副本確認。如果達到以毫秒為單位指定的超時,則即使尚未達到指定數量的副本,該命令也會返回。
WAIT 命令同步複製也並不能保證強一致性,不過在主節點當機之後,只不過會盡可能的選擇最佳的副本(slaves)

原始碼在這一部分。

看原始碼,同時傳送了一個 WAIT 1 1000 到 Redis。

結論

Redisson RedLock 是基於聯鎖 MultiLock 實現的,但是使用過程中需要自己判斷 key 落在哪個節點上,對使用者不是很友好。

Redisson RedLock 已經被棄用,直接使用普通的加鎖即可,會基於 wait 機制將鎖同步到從節點,但是也並不能保證一致性。僅僅是最大限度的保證一致性。

相關推薦

相關文章