redis中紅鎖的使用小結(jié)
好的,我們來(lái)詳細(xì)、深入地探討一下 Redis 紅鎖(RedLock)。
RedLock 是 Redis 官方提出的一種用于在分布式環(huán)境下實(shí)現(xiàn)強(qiáng)一致性分布式鎖的算法。它的提出,是為了解決在 Redis ?主從復(fù)制或哨兵模式下,使用單實(shí)例 Redis 鎖可能遇到的鎖失效問(wèn)題。
1. 為什么需要紅鎖?—— 問(wèn)題的起源
在深入了解紅鎖之前,必須先理解它要解決什么問(wèn)題。
假設(shè)我們使用單個(gè) Redis 實(shí)例實(shí)現(xiàn)分布式鎖(通常用 SET key random_value NX PX 30000命令):
- 客戶端 A 在 Master 節(jié)點(diǎn)上成功獲取鎖。
- 在 Master 將鎖數(shù)據(jù)同步到 Slave 節(jié)點(diǎn)之前,Master 宕機(jī)了。
- 哨兵機(jī)制觸發(fā),一個(gè) Slave 節(jié)點(diǎn)升級(jí)為新的 Master。
- 此時(shí),這個(gè)新的 Master 節(jié)點(diǎn)上并沒(méi)有客戶端 A 持有的鎖。
- 客戶端 B 向新的 Master 節(jié)點(diǎn)申請(qǐng)鎖,成功獲取。此時(shí),?客戶端 A 和客戶端 B 同時(shí)認(rèn)為自己持有了鎖,導(dǎo)致鎖的安全性被破壞。
?核心問(wèn)題?:在異步復(fù)制的場(chǎng)景下,鎖數(shù)據(jù)在寫(xiě)入主節(jié)點(diǎn)后,到同步到從節(jié)點(diǎn)之間存在一個(gè)時(shí)間窗口。在這個(gè)窗口內(nèi)主節(jié)點(diǎn)故障,會(huì)導(dǎo)致鎖數(shù)據(jù)的丟失。
紅鎖的目標(biāo)就是消除對(duì)單個(gè) Redis 實(shí)例的依賴,從而避免這類問(wèn)題。
2. 紅鎖算法(RedLock Algorithm)的核心思想
紅鎖的基本思想非常直觀:??“不要把所有雞蛋放在一個(gè)籃子里”?。
它要求客戶端向一個(gè)獨(dú)立的、無(wú)主從關(guān)系的 Redis 實(shí)例集群中的多數(shù)(N/2+1)個(gè)節(jié)點(diǎn)依次申請(qǐng)鎖,只有當(dāng)從多數(shù)節(jié)點(diǎn)都獲取鎖成功,并且總耗時(shí)小于鎖的有效時(shí)間,才算最終加鎖成功。
算法前提條件
?部署多個(gè) Redis Master 節(jié)點(diǎn)?:這些節(jié)點(diǎn)必須是完全獨(dú)立的,相互之間沒(méi)有數(shù)據(jù)同步關(guān)系(例如,不是同一個(gè)哨兵或集群下的節(jié)點(diǎn))。建議至少 5 個(gè)節(jié)點(diǎn),這樣可以容忍其中 2 個(gè)節(jié)點(diǎn)故障,從而保證系統(tǒng)的可用性。
算法步驟詳解
假設(shè)我們有 N 個(gè) Redis 節(jié)點(diǎn)(例如 N=5)。
?第一步:獲取當(dāng)前時(shí)間?
客戶端在開(kāi)始獲取鎖之前,先記錄一個(gè)開(kāi)始時(shí)間 start_time。
?第二步:依次向所有 N 個(gè)節(jié)點(diǎn)申請(qǐng)鎖?
客戶端使用相同的鍵名和隨機(jī)值,依次向 5 個(gè) Redis 實(shí)例發(fā)送鎖申請(qǐng)命令(SET lock_name my_random_value NX PX 30000)。
為了減少因?yàn)槟硞€(gè)節(jié)點(diǎn)故障而造成的長(zhǎng)時(shí)間等待,可以為每個(gè)節(jié)點(diǎn)的請(qǐng)求設(shè)置一個(gè)遠(yuǎn)小于鎖超時(shí)時(shí)間的網(wǎng)絡(luò)超時(shí)時(shí)間(例如,鎖超時(shí) 10 秒,網(wǎng)絡(luò)超時(shí) 50-100 毫秒)。如果一個(gè)節(jié)點(diǎn)沒(méi)有響應(yīng),應(yīng)盡快嘗試下一個(gè)節(jié)點(diǎn)。
?第三步:計(jì)算獲取鎖的總耗時(shí)?
當(dāng)客戶端從所有節(jié)點(diǎn)都收到響應(yīng)(無(wú)論是成功還是失敗)后,記錄結(jié)束時(shí)間 end_time。計(jì)算總耗時(shí):total_time = end_time - start_time。
?第四步:檢查鎖是否獲取成功?
客戶端需要同時(shí)滿足以下兩個(gè)條件,才認(rèn)為鎖獲取成功:
- ?多數(shù)派原則?:客戶端從至少
N/2 + 1個(gè)節(jié)點(diǎn)(對(duì)于 5 個(gè)節(jié)點(diǎn),就是 3 個(gè))上成功獲取了鎖。 - ?有效性檢查?:獲取鎖的總耗時(shí)
total_time必須小于鎖的自動(dòng)釋放時(shí)間(TTL)。例如,鎖的 TTL 是 10 秒,而total_time是 2 秒,那么是有效的。如果total_time是 12 秒,則無(wú)效。
?為什么需要條件 2??? 這是為了防止客戶端在獲取鎖的過(guò)程中耗時(shí)太久,導(dǎo)致最早申請(qǐng)到的那些鎖在客戶端開(kāi)始執(zhí)行關(guān)鍵代碼前就已經(jīng)過(guò)期了。
?如果鎖獲取失敗?:客戶端必須向所有 Redis 節(jié)點(diǎn)發(fā)起釋放鎖的請(qǐng)求?(即執(zhí)行 Lua 腳本,檢查隨機(jī)值并刪除鎖)。即使某些節(jié)點(diǎn)返回失敗(比如它本來(lái)就沒(méi)加鎖成功),也需要嘗試釋放,以確保清理現(xiàn)場(chǎng)。
3. 釋放鎖
釋放鎖的過(guò)程相對(duì)簡(jiǎn)單:客戶端需要向第一步中嘗試獲取鎖的所有 N 個(gè)節(jié)點(diǎn)發(fā)送釋放命令。
釋放命令必須使用 Lua 腳本保證原子性:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end這樣做是為了確保只有鎖的持有者才能釋放鎖,避免誤刪其他客戶端創(chuàng)建的鎖。
4. 紅鎖的爭(zhēng)議與局限性(非常重要?。?/h2>
RedLock 自提出以來(lái),就在分布式系統(tǒng)領(lǐng)域引發(fā)了激烈的討論,特別是來(lái)自 Martin Kleppmann(《數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計(jì)》作者)的挑戰(zhàn)。理解這些爭(zhēng)議點(diǎn)對(duì)于正確使用紅鎖至關(guān)重要。
主要爭(zhēng)議點(diǎn):
?對(duì)系統(tǒng)時(shí)鐘(時(shí)鐘跳躍)的敏感性?
- ?場(chǎng)景?:假設(shè)客戶端 1 持有鎖。某個(gè) Redis 節(jié)點(diǎn)因?yàn)橄到y(tǒng)時(shí)鐘被調(diào)整(例如通過(guò) NTP 同步或人為修改),導(dǎo)致其上的鎖提前過(guò)期。
- ?問(wèn)題?:客戶端 2 向這個(gè)節(jié)點(diǎn)申請(qǐng)鎖,可能成功。如果客戶端 2 又從其他節(jié)點(diǎn)成功獲取了足夠多的鎖,那么紅鎖算法就會(huì)認(rèn)為客戶端 2 獲得了鎖,從而導(dǎo)致兩個(gè)客戶端同時(shí)進(jìn)入臨界區(qū)。
- ?紅鎖的辯護(hù)?:Redis 作者 Antirez 認(rèn)為,應(yīng)該通過(guò)合理的運(yùn)維手段禁止這種跳躍式的時(shí)鐘調(diào)整,而使用“慢速收斂”的時(shí)鐘同步方式。
?GC Pause(垃圾回收暫停)或進(jìn)程暫停帶來(lái)的安全性問(wèn)題?
?場(chǎng)景(Martin 描述的著名例子)??:
- 客戶端 1 成功獲得紅鎖,并開(kāi)始執(zhí)行關(guān)鍵代碼。
- 在執(zhí)行過(guò)程中,發(fā)生了長(zhǎng)時(shí)間的 GC Pause(例如 30 秒),導(dǎo)致客戶端 1 的進(jìn)程被“凍結(jié)”。
- 在此期間,客戶端 1 持有的鎖因?yàn)槌瑫r(shí)(TTL 到期)而被所有 Redis 節(jié)點(diǎn)自動(dòng)釋放。
- 客戶端 2 成功獲得了紅鎖,并開(kāi)始執(zhí)行關(guān)鍵代碼。
- 客戶端 1 從 GC Pause 中恢復(fù),繼續(xù)執(zhí)行關(guān)鍵代碼。此時(shí),?客戶端 1 和客戶端 2 再次同時(shí)進(jìn)入了臨界區(qū)。
?問(wèn)題的本質(zhì)?:紅鎖(以及任何基于超時(shí)的鎖)無(wú)法區(qū)分“客戶端業(yè)務(wù)邏輯執(zhí)行緩慢”和“客戶端進(jìn)程已崩潰”這兩種情況。它依賴于超時(shí)機(jī)制,而超時(shí)機(jī)制在存在長(zhǎng)時(shí)間進(jìn)程暫停時(shí)就會(huì)失效。
?解決方案的討論?:Martin 提出需要使用一種能夠在共享存儲(chǔ)中留下“圍欄令牌”(fencing token)?? 的鎖服務(wù)??蛻舳嗽趯?xiě)數(shù)據(jù)時(shí),需要檢查一個(gè)單調(diào)遞增的令牌,以確保自己的操作是在最新的鎖狀態(tài)下進(jìn)行的。這實(shí)際上要求共享資源層(如 Zookeeper、etcd)提供額外的協(xié)調(diào)能力。
5. 紅鎖的使用建議
考慮到上述爭(zhēng)議,你應(yīng)該在以下情況下考慮使用紅鎖:
- ?對(duì)一致性要求極高?:你確實(shí)需要一把強(qiáng)一致的鎖,并且可以接受紅鎖帶來(lái)的性能下降(因?yàn)樾枰c多個(gè)節(jié)點(diǎn)通信)。
- ?可以控制運(yùn)維環(huán)境?:你能夠確保 Redis 節(jié)點(diǎn)所在的機(jī)器不會(huì)發(fā)生劇烈的時(shí)鐘漂移。
- ?理解其局限性?:你清楚地知道,在極端情況下(如長(zhǎng)時(shí)間的進(jìn)程暫停),紅鎖仍然無(wú)法提供 100% 的安全保證。對(duì)于大多數(shù)業(yè)務(wù)場(chǎng)景,這種極端情況的發(fā)生概率和其帶來(lái)的風(fēng)險(xiǎn)是可以接受的。
?替代方案?:
- ?對(duì)于高可用要求不極端的場(chǎng)景?:使用 Redis 主從+哨兵模式,并接受在主從切換的極小時(shí)間窗口內(nèi)可能出現(xiàn)的鎖失效問(wèn)題。很多業(yè)務(wù)場(chǎng)景下,這種風(fēng)險(xiǎn)是可接受的。
- ?對(duì)于必須強(qiáng)一致的場(chǎng)景?:考慮使用專門為分布式協(xié)調(diào)而設(shè)計(jì)的系統(tǒng),如 ?ZooKeeper? 或 ?etcd。這些系統(tǒng)使用了共識(shí)算法(如 Zab、Raft),它們本身就是為了在分布式環(huán)境下提供強(qiáng)一致性和容錯(cuò)性而設(shè)計(jì)的,實(shí)現(xiàn)分布式鎖是它們的原生強(qiáng)項(xiàng),通常能提供比紅鎖更可靠的安全保證。
總結(jié)
特性 | 描述 |
|---|---|
?目標(biāo)? | 在分布式 Redis 環(huán)境下實(shí)現(xiàn)一個(gè)更安全、強(qiáng)一致的分布式鎖。 |
?核心思想? | 向多個(gè)獨(dú)立的 Redis 主節(jié)點(diǎn)申請(qǐng)鎖,遵循“多數(shù)派”原則。 |
?優(yōu)點(diǎn)? | 解決了單點(diǎn) Redis 和主從架構(gòu)下因異步復(fù)制導(dǎo)致的鎖失效問(wèn)題。 |
?缺點(diǎn)/爭(zhēng)議? | 1. 對(duì)系統(tǒng)時(shí)鐘漂移敏感。 |
?適用場(chǎng)景? | 對(duì)鎖的一致性要求非常高,且愿意為了一致性犧牲部分性能和增加復(fù)雜度的場(chǎng)景。 |
?替代方案? | Redis 主從鎖(可接受風(fēng)險(xiǎn))、ZooKeeper、etcd。 |
到此這篇關(guān)于redis中紅鎖的使用小結(jié)的文章就介紹到這了,更多相關(guān)redis 紅鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
攔截Redis命令導(dǎo)致的Lua腳本執(zhí)行失敗的問(wèn)題解決
本文主要介紹了攔截Redis命令導(dǎo)致的Lua腳本執(zhí)行失敗的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
關(guān)于Redis網(wǎng)絡(luò)模型的源碼詳析
這篇文章主要給大家介紹了關(guān)于Redis網(wǎng)絡(luò)模型的源碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Redis為什么快如何實(shí)現(xiàn)高可用及持久化
這篇文章主要介紹了Redis為什么快如何實(shí)現(xiàn)高可用及持久化,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Redis本地/遠(yuǎn)程(外部)連接失敗問(wèn)題及解決
這篇文章主要介紹了Redis本地/遠(yuǎn)程(外部)連接失敗問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Windows系統(tǒng)安裝redis數(shù)據(jù)庫(kù)
這篇文章介紹了Windows系統(tǒng)安裝redis數(shù)據(jù)庫(kù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03

