當前位置:網站首頁>再探 redis 分布式鎖

再探 redis 分布式鎖

2022-01-27 17:48:50 看,未來

在這裏插入圖片描述

前言

“分布式鎖”這個話題在程序界有很大的關注量,引發了不少討論。關於分布式鎖有很多實現的方案,本文就講基於 Redis 實現的分布式鎖。一些基本的東西我就直接帶過吧。

問:為什麼需要分布式鎖?
答:以前為什麼需要互斥鎖?

Redis 分布式鎖的演進:

1、setnx
存在問題:若上鎖的實例還沒解鎖就掛了,就死鎖了。
解决方案:為鎖設置一個過期時間。
2、setnx px,為鎖設置一個過期時間。
存在問題:鎖過期了,但是任務還沒完成,鎖就被釋放了。當上鎖者完成任務後,容易釋放別的業務上的鎖。

解决方案:可以采用看門狗進程,當鎖要過期了就給它延時;也可以為鎖設置一個token,只有token匹配上了才能解鎖。
3、setnx key token px time
這裏不采用看門狗進程的解法,具體可自己思考。

存在問題:若在解鎖做 token 判斷成功之後,解鎖線程出現了調度,或延時,當真正操作 del 的時候鎖已經過期了,則將把別的業務上的鎖給解鎖掉。

解决方案:將解鎖過程寫入 lua 脚本,將解鎖過程原子化。調用 lua 脚本解鎖。

lua 脚本如下:

--判斷鎖是自己的才釋放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else    
    return 0
end

到這裏,一個單節點的 redis 鎖便完成了它的使命。

但是,分布式到這裏還沒鋪開呢!!!


主從切換

我們在使用 Redis 時,一般會采用主從集群 + 哨兵的模式部署。當主從發生切換的時候,這個分布式鎖依舊安全麼?

在這裏插入圖片描述

1、客戶端1在主庫上上鎖。
2、主庫宕機,setnx 命令還未到達從庫。
3、從庫被哨兵提昇為新主庫。
4、該鎖在新主庫上查不到,則可以由另一個客戶端上鎖。

於是又開始俄羅斯套娃了。

為此,Redis 的作者提出一種解决方案,就是我們經常聽到的 Redlock(紅鎖)。


Redlock 紅鎖

Redlock 的方案基於 2 個前提:
1、不再需要部署從庫和哨兵實例,只部署主庫。
2、但主庫要部署多個,官方推薦至少 5 個實例。

問:不做主從,那萬一某個庫宕機了,數據不就都丟了嗎?
答:丟就丟了唄,本來就不是用來存數據的。

具體流程如下:

1、客戶端先獲取當前時間戳T1。
2、客戶端依次向這 5 個 Redis 實例發起加鎖請求,且每個請求會設置超時時間(毫秒級,要遠小於鎖的有效時間),
如果某一個實例加鎖失敗(包括網絡超時、鎖被其它人持有等各種异常情况),就立即向下一個 Redis 實例申請加鎖。
3、如果客戶端從 >=3 個(大多數)以上 Redis 實例加鎖成功,則再次獲取「當前時間戳T2」,如果 T2 - T1 < 鎖的過期時間,此時,認為客戶端加鎖成功,否則認為加鎖失敗。
4、加鎖成功,去操作共享資源。
5、加鎖失敗,向「全部節點」發起釋放鎖請求。

問:我也不貪心,我 5 個實例來共同競爭,需要競爭多少遍(失敗一遍就要全部釋放)?
答:emmm…emmm…emmm…(容我三思)(思不出來)

問:為什麼要在多個節點上上鎖?
答:防止某些個節點因為各種原因不能提供服務,且沒來得及恢複。

問:為什麼釋放鎖,要操作所有節點?
答:無則加勉,有則改之。

二三那倆問題是網上提的比較多的,問題一是我自己提的,結果自己答不上來。。。


分布式系統專家馬丁的質疑

馬丁寫了一篇文章(How to do distributed locking),錶達了自己對於紅鎖的質疑。在他的文章中主要闡述了四個觀點:
1、分布式鎖的目的是什麼?
2、鎖在分布式系統中會遇到的問題。
3、假設時鐘正確是不合理的。
4、提出 fencing token 的方案,保證正確性。

我們一一來看:


1、分布式鎖的目的。
他認為有兩個目的:

1、效率。
2、正確性。

如果是為了效率,那麼單機版的 Redis 就可以了,即使偶爾鎖失效,也是可以理解的。
如果是為了正確性,馬丁認為 Redlock 根本達不到分布式安全的要求,依舊存在鎖失效的問題。

(其實我沒想明白上鎖提昇效率,是什麼情况。)


2、鎖在分布式系統中會遇到的問題
Martin 錶示,一個分布式系統,更像一個複雜的「野獸」,存在著你想不到的各種异常情况。

這些异常場景主要包括三大塊,這也是分布式系統會遇到的三座大山:NPC。

N:Network Delay,網絡延遲
P:Process Pause,進程暫停(GC)
C:Clock Drift,時鐘漂移

Martin 用一個進程暫停(GC)的例子,指出了 Redlock 安全性問題:

1、客戶端 1 請求鎖定節點 A、B、C、D、E
2、客戶端 1 的拿到鎖後,進入 GC(時間比較久)
3、所有 Redis 節點上的鎖都過期了
4、客戶端 2 獲取到了 A、B、C、D、E 上的鎖
5、客戶端 1 GC 結束,認為成功獲取鎖
6、客戶端 2 也認為獲取到了鎖,發生「沖突」

Martin 認為,GC 可能發生在程序的任意時刻,而且執行時間是不可控的。

當然,即使是使用沒有 GC 的編程語言,在發生網絡延遲、時鐘漂移時,也都有可能導致 Redlock 出現問題,這裏 Martin 只是拿 GC 舉例。


3、假設時鐘正確是不合理的。

客戶端C1獲得了對節點A、B、c的鎖定,由於網絡問題,法到達節點D和節點E

節點C上的時鐘向前跳,導致鎖提前過期

客戶端C2在節點C、D、E上獲得鎖定,由於網絡問題,無法到達A和B

客戶端C1和客戶端C2現在都認為他們自己持有鎖

前跳應該是不至於了,不過系統時鐘是存在誤差的,可以看我前面發的那篇:
計算機時鐘是如何運行的?


4、提出 fencing token 的方案,保證正確性

1、客戶端在獲取鎖時,鎖服務可以提供一個「遞增」的 token
2、客戶端拿著這個 token 去操作共享資源
3、共享資源可以根據 token 拒絕「後來者」的請求

這樣一來,無論 NPC 哪種异常情况發生,都可以保證分布式鎖的安全性,因為它是建立在「异步模型」上的。

他還錶示,一個好的分布式鎖,無論 NPC 怎麼發生,可以不在規定時間內給出結果,但並不會給出一個錯誤的結果。也就是只會影響到鎖的「性能」(或稱之為活性),而不會影響它的「正確性」。


Redis 作者 Antirez 的反駁

在 Redis 作者的文章中,重點有 3 個:

1)解釋時鐘問題。
Redis 作者錶示,Redlock 並不需要完全一致的時鐘,只需要大體一致就可以了,允許有「誤差」。
(不曉得哦,前面不是要計算時間嗎?)

2)解釋網絡延遲、GC 問題
Redis 作者强調:如果在 1-3 發生了網絡延遲、進程 GC 等耗時長的异常情况,那在第 3 步 T2 - T1,是可以檢測出來的,如果超出了鎖設置的過期時間,那這時就認為加鎖會失敗,之後釋放所有節點的鎖就好了!

(但是如果是鎖已經拿到手上了呢,拿到手之後GC)

  1. 質疑 fencing token 機制
    第一,這個方案必須要求要操作的「共享資源服務器」有拒絕「舊 token」的能力。
    第二,退一步講,即使 Redlock 沒有提供 fencing token 的能力,但 Redlock 已經提供了隨機值(就是前面講的 UUID),利用這個隨機值,也可以達到與 fencing token 同樣的效果。

我的思考

我想了好久,從上次接觸 redis 分布式鎖開始,因為沒有思考清楚,我的畢設一直處於遲緩進度狀態。

1、從生產實際出發。甘瓜苦蒂,天下物無全美。我們為什麼要要求一套方案解决所有的問題呢?我只要這一套方案解决我的問題就行了,別人想借鑒,就借鑒唄,但不保證和他的需求百分百契合。

2、所以我的畢設决定采用:

setnx px 
	+
daemon
	+
fencing token
	+ 
lua

的方案,有幾個點:
1、fencing token 實現方案:高並發下唯一 ID 生成方案
2、daemon 有限續命,如果占據鎖的實例掛了,不會無休止的續命導致鎖一直無法釋放。且續命到達一定時長將寫入日志,作為慢業務進行優化處理。
3、對於主從替換導致的分布式鎖失效,做成 集群 + 主從,降低單節點被打崩的風險,也降低單節點承載的業務量。萬一真被打崩了,不至於全軍覆沒。且由於 fencing token 的存在,可以將損失控制在很低的比率。
畢竟我的畢設裏面實時數據快速變化的場景也就那麼一兩秒,平常流量不會那麼大。

版權聲明
本文為[看,未來]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201271748501664.html

隨機推薦