1. 程式人生 > 其它 >Redisson 分散式鎖原始碼 09:RedLock 紅鎖的故事

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

RedLock 紅鎖,是分散式鎖中必須要了解的一個概念。 所以本文會先介紹什麼是 RedLock,當大家對 RedLock 有一個基本的瞭解。然後再看 Redisson 中是如何實現 RedLock 的。

前言

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 機制將鎖同步到從節點,但是也並不能保證一致性。僅僅是最大限度的保證一致性。

相關推薦

作者: 劉志航

公眾號:『 程式設計師小航 』

個人小站:https://liuzhihang.com/

版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 Notes