精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面試官:如何保證分布式鎖的高可用和高性能?

開(kāi)發(fā) 前端
從最初的 ??SETNX?? 到完善的續(xù)約、原子化釋放、再到 Redlock 以及各種性能優(yōu)化手段,我們可以看到,分布式鎖的核心始終圍繞兩個(gè)關(guān)鍵詞展開(kāi):可靠性與性能。

現(xiàn)在基本上所有的后端都是分布式系統(tǒng),數(shù)據(jù)一致性是我們所有后端工程師必須面對(duì)的核心挑戰(zhàn)。而要保證一致性,鎖就是我們繞不開(kāi)的工具。從單體應(yīng)用中的 synchronized 或 ReentrantLock,到分布式環(huán)境下的分布式鎖,其本質(zhì)一脈相承,即確保在多線程或多節(jié)點(diǎn)環(huán)境下,某個(gè)關(guān)鍵資源在同一時(shí)刻只能被一個(gè)執(zhí)行單元訪問(wèn)。

分布式鎖和分布式事務(wù),堪稱分布式系統(tǒng)中的兩大“攔路虎”,理論深?yuàn)W,實(shí)踐中又極易出錯(cuò)。在面試中,分布式鎖更是高階崗位的必考題。然而,還是有很多同學(xué)對(duì)分布式鎖的理解僅僅停留在 SETNX 和“設(shè)置一個(gè)過(guò)期時(shí)間”的表層。當(dāng)被進(jìn)一步追問(wèn),比如“如何優(yōu)雅地等待鎖?”、“加鎖超時(shí)了怎么辦?”、“業(yè)務(wù)執(zhí)行時(shí)間超過(guò)了鎖的過(guò)期時(shí)間又該如何?”時(shí),就開(kāi)始不知道怎么回答了。

一個(gè)工業(yè)級(jí)的分布式鎖,需要考慮的遠(yuǎn)不止于此。今天,我們就以 Redis 為例,從零開(kāi)始,層層深入,一步步構(gòu)建一個(gè)真正高可用、高性能的分布式鎖方案,確保你在這個(gè)話題下?lián)碛凶銐虻纳疃取?/span>

1. 加鎖

首先要明確,并非只有 Redis 才能實(shí)現(xiàn)分布式鎖。廣義上講,任何支持“排他性操作”的中間件都可以。比如ZooKeeper、Nacos 甚至關(guān)系型數(shù)據(jù)庫(kù)(如利用 MySQL 的 SELECT ... FOR UPDATE 語(yǔ)法)。但鑒于 Redis 極高的性能、廣泛的應(yīng)用基礎(chǔ)以及其提供的豐富指令,它成為了實(shí)現(xiàn)分布式鎖的最主流選擇。

要用 Redis 實(shí)現(xiàn)鎖,我們首先要理解鎖的本質(zhì)——排他性。我們只需要在 Redis 中找到一種排他性的操作即可。SETNXSET if Not eXists)命令完美契合了這個(gè)需求。此命令只在 key 不存在時(shí)才會(huì)設(shè)置成功,并返回1;如果 key 已存在,則設(shè)置失敗,返回0。因此,一個(gè)最簡(jiǎn)單的分布式鎖模型就誕生了:

  • 加鎖:執(zhí)行 SETNX lock_key any_value。如果返回1,代表加鎖成功。
  • 釋放鎖:執(zhí)行 DEL lock_key

11

如上圖所示,線程1執(zhí)行 SETNX key1=value1 成功,獲得了鎖。隨后線程2執(zhí)行 SETNX key1=value2 失敗,進(jìn)入等待。這個(gè)模型雖然簡(jiǎn)單,但在實(shí)際應(yīng)用中卻有很多漏洞,根本無(wú)法投入生產(chǎn)。我們來(lái)逐一看看它暴露出的問(wèn)題。

1.1 等待與重試

當(dāng) SETNX 失敗時(shí),客戶端不應(yīng)立即返回失敗,而應(yīng)進(jìn)入等待”段。但如何等待,以及在等待過(guò)程中遇到網(wǎng)絡(luò)超時(shí),都大有講究。

1.1.1 如何等待鎖

當(dāng)加鎖失敗,我們有兩種主流的等待策略:

  • 循環(huán)輪詢(Spin Lock)

這是最簡(jiǎn)單粗暴的方案。比如,加鎖失敗后,線程 sleep 100毫秒,然后再次嘗試 SETNX,直到加鎖成功或超出總的等待超時(shí)時(shí)間。這個(gè)“總的等待超時(shí)時(shí)間”該如何設(shè)定?這需要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)定。我們應(yīng)該盡可能確保在等待時(shí)間內(nèi)能拿到鎖。因此,這個(gè)時(shí)間應(yīng)該約等于一個(gè)鎖的平均持有時(shí)間。例如,如果我們通過(guò)監(jiān)控統(tǒng)計(jì),發(fā)現(xiàn)99%的業(yè)務(wù)執(zhí)行時(shí)間都在800毫秒內(nèi),那么將總等待時(shí)間設(shè)為1秒就是個(gè)合理的選擇。

這種方式的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,缺點(diǎn)是會(huì)帶來(lái)不必要的 sleep(可能鎖剛被釋放,但線程還在睡眠)和頻繁的 Redis 查詢(無(wú)效輪詢),但是實(shí)時(shí)性不好且空耗資源。

  • 事件監(jiān)聽(tīng)

輪詢方式顯然不夠優(yōu)雅。更高級(jí)的方式是利用 Redis 的發(fā)布訂閱(Pub/Sub)機(jī)制,或者更高階的 Keyspace Notifications(鍵空間通知)。加鎖失敗的客戶端可以立即“訂閱”lock_key 的“刪除事件”(DEL 事件)。當(dāng)鎖被釋放(DEL)時(shí),Redis 會(huì)主動(dòng)通知所有訂閱者,它們?cè)僦匦聡L試加鎖(SETNX)。這種方式實(shí)時(shí)性更好,也更節(jié)省客戶端和服務(wù)端資源,但實(shí)現(xiàn)起來(lái)也相對(duì)復(fù)雜。

22

注意:上圖只是一個(gè)示意。在實(shí)際工程中,"檢測(cè)到鎖存在" 和 "發(fā)起訂閱" 必須是一個(gè)原子化或有嚴(yán)密邏輯先后的操作,否則可能在客戶端檢測(cè)到鎖存在(SETNX 失敗)和發(fā)起訂閱的這個(gè)微小間隙,鎖恰好被釋放了(DEL),導(dǎo)致客戶端錯(cuò)過(guò)通知,陷入永久等待。

1.1.2 如何重試加鎖?

分布式環(huán)境中,網(wǎng)絡(luò)抖動(dòng)是常態(tài)。如果一個(gè)客戶端發(fā)起 SETNX 命令后,收到了一個(gè)超時(shí)響應(yīng),這時(shí)它會(huì)陷入薛定諤的鎖狀態(tài):我到底加上鎖了嗎?

如果貿(mào)然重試,可能會(huì)覆蓋一個(gè)已經(jīng)加鎖成功的狀態(tài)。解決這個(gè)問(wèn)題的核心在于保證操作的冪等性。我們必須讓鎖的持有者能夠“識(shí)別”自己的鎖。具體做法是,在加鎖時(shí),value 不再是任意值,而是一個(gè)全局唯一的ID,例如 UUID 或是唯一的請(qǐng)求ID(Request ID)。

假設(shè)我們?yōu)?nbsp;lock:order:555 這把鎖生成了唯一值 uuid-client-A。當(dāng)重試時(shí),邏輯如下:

客戶端發(fā)起 SETNX lock:order:555 uuid-client-A,不幸超時(shí)。客戶端發(fā)起重試。此時(shí)它不能直接 SETNX,而是應(yīng)該先 GET lock:order:555

  • 情況A:GET 返回 nil(key不存在)

這說(shuō)明上一次的 SETNX 命令沒(méi)有到達(dá) Redis 或執(zhí)行失敗。客戶端此時(shí)可以安全地再次執(zhí)行 SETNX lock:order:555 uuid-client-A。

33

  • 情況B:GET 返回 uuid-client-A。

這說(shuō)明上一次的 SETNX 執(zhí)行成功了!只是響應(yīng)包在路上丟了。此時(shí)客戶端已經(jīng)持有了鎖,它需要做的只是重置一下這把鎖的過(guò)期時(shí)間(我們稍后會(huì)講到),然后心滿意足地返回“加鎖成功”。

44

  • 情況C:GET 返回 uuid-client-B。

這說(shuō)明在客戶端A超時(shí)和重試的間隙,鎖被客戶端B拿走了(可能是A上次加鎖失敗,B加鎖成功)。此時(shí)客戶端A重試失敗,應(yīng)進(jìn)入上述的等待邏輯。

55

在分布式鎖的設(shè)計(jì)中,如果要支持 重試機(jī)制,關(guān)鍵是要能夠判斷上一次加鎖是否成功。因此,在加鎖時(shí),可以為每個(gè)鎖的值設(shè)置一個(gè)唯一標(biāo)識(shí)(例如一個(gè)隨機(jī)生成的 UUID),用于標(biāo)識(shí)鎖的持有者。

當(dāng)客戶端因超時(shí)等原因需要重試時(shí),可以根據(jù)以下邏輯進(jìn)行判斷:

  • 查詢鎖是否存在:如果 Redis 中不存在該 key,說(shuō)明上次加鎖沒(méi)有成功,可以直接重新嘗試加鎖。
  • 校驗(yàn)鎖的歸屬:如果 key 存在,并且其 value 與本次請(qǐng)求攜帶的 UUID 相同,說(shuō)明上次加鎖其實(shí)已經(jīng)成功了。此時(shí),為了防止鎖過(guò)期失效,可以重新設(shè)置鎖的過(guò)期時(shí)間。
  • 鎖已被他人持有:如果 key 存在,但 value 與本次 UUID 不同,說(shuō)明鎖已經(jīng)被其他客戶端持有,本次加鎖應(yīng)視為失敗。

在實(shí)踐中,由于 Redis 性能極高,這種加鎖超時(shí)的情況很少見(jiàn),一般重試一兩次足矣。但是,如果我們從理論上分析,萬(wàn)一重試也一直超時(shí)呢?

其實(shí)也無(wú)需額外處理。如果之前加鎖已經(jīng)成功了(情況B),那么無(wú)非就是后續(xù)的重置過(guò)期時(shí)間失敗,等鎖到了過(guò)期時(shí)間,自然就失效了。如果之前沒(méi)加鎖成功(情況A或C),那就更沒(méi)關(guān)系了,別的線程需要時(shí)自然可以拿到鎖。這也就自然而然地引出了下一個(gè)關(guān)鍵概念:過(guò)期時(shí)間

1.2 鎖過(guò)期與續(xù)約

我們剛才的簡(jiǎn)單模型(SETNX + DEL)還有一個(gè)致命缺陷:如果一個(gè)客戶端加鎖成功后,業(yè)務(wù)還沒(méi)執(zhí)行完就宕機(jī)了——比如機(jī)器斷電、進(jìn)程崩潰——它將永遠(yuǎn)無(wú)法執(zhí)行 DEL。這把鎖就成了“死鎖”,其他所有客戶端都將永遠(yuǎn)等待下去,導(dǎo)致整個(gè)業(yè)務(wù)線癱瘓。

66

1.2.1 為什么需要過(guò)期時(shí)間?

為了防止持有者宕機(jī)導(dǎo)致死鎖,我們必須引入一道保險(xiǎn)——鎖過(guò)期時(shí)間。我們必須在加鎖的同時(shí),給鎖設(shè)置一個(gè)過(guò)期時(shí)間(TTL)。這里要強(qiáng)調(diào)一下,加鎖(SETNX)和設(shè)置過(guò)期時(shí)間(EXPIRE)必須是一個(gè)原子操作。否則,如果在 SETNX 成功和 EXPIRE 之間客戶端宕機(jī),依然會(huì)產(chǎn)生死鎖。

這里我們Redis 提供了原子命令:

SET lock_key unique_value EX 30 NX

這條命令(SET ... EX ... NX)等價(jià)于 SETNX + EXPIRE,完美解決了原子性問(wèn)題。這樣,即便持有鎖的客戶端宕機(jī),鎖也會(huì)在30秒后自動(dòng)釋放,其他客戶端得以繼續(xù)工作。

77

1.2.2 過(guò)期時(shí)間設(shè)多長(zhǎng)?

只要引入了過(guò)期時(shí)間,面試官幾乎必然會(huì)問(wèn):這個(gè)時(shí)間應(yīng)該設(shè)置成多長(zhǎng)?這又是一個(gè)經(jīng)典問(wèn)題。答案還是:根據(jù)業(yè)務(wù)場(chǎng)景


你需要評(píng)估業(yè)務(wù)邏輯的執(zhí)行耗時(shí),比如取 99.9% 分位線(P999),然后再加一個(gè)合理的緩沖(Buffer)。例如,99.9% 的請(qǐng)求在5秒內(nèi)完成,你可以把過(guò)期時(shí)間設(shè)為10秒甚至20秒。

這里還有一個(gè)重要觀點(diǎn):過(guò)期時(shí)間的主要目的是為了防止宕機(jī)導(dǎo)致的死鎖。而在絕大多數(shù)(99.99%)情況下,鎖都應(yīng)該由客戶端在業(yè)務(wù)執(zhí)行完畢后主動(dòng)釋放(DEL)。因此,把過(guò)期時(shí)間設(shè)得長(zhǎng)一些,比如30秒、1分鐘,通常是安全的,也是合理的。

其實(shí)很多公司的分布式鎖實(shí)現(xiàn),也就到這一步為止了。但我們還可以繼續(xù)深挖。

1.2.3 鎖續(xù)約機(jī)制

無(wú)論你把過(guò)期時(shí)間設(shè)為30秒還是1分鐘,總有可能遇到“天選之子”——某個(gè)請(qǐng)求因?yàn)镚C停頓、網(wǎng)絡(luò)延遲或極其復(fù)雜的計(jì)算,執(zhí)行時(shí)間真的超過(guò)了鎖的過(guò)期時(shí)間。這時(shí),會(huì)發(fā)生什么?

  • 客戶端A持有鎖,業(yè)務(wù)正在執(zhí)行。
  • 鎖到達(dá)1分鐘過(guò)期時(shí)間,被 Redis 自動(dòng)釋放。
  • 另一個(gè)客戶端B立即通過(guò) SETNX 拿到了這把鎖,開(kāi)始執(zhí)行業(yè)務(wù)。
  • 緊接著,客戶端A的業(yè)務(wù)終于執(zhí)行完畢,它會(huì)(錯(cuò)誤地)去釋放鎖(我們稍后會(huì)講如何避免)。
  • 這就造成了數(shù)據(jù)混亂,兩個(gè)客戶端可能同時(shí)操作了資源。

88

為了解決這個(gè)問(wèn)題,我們需要引入鎖續(xù)約(Renewal)機(jī)制,也常被稱為“看門狗(Watchdog)”機(jī)制。原理是:客戶端在加鎖成功后,啟動(dòng)一個(gè)后臺(tái)線程(或定時(shí)任務(wù))。這個(gè)線程會(huì)周期性地檢查客戶端是否還持有鎖。如果還持有(即業(yè)務(wù)尚未執(zhí)行完),就去“續(xù)”一下鎖的過(guò)期時(shí)間,比如重新 EXPIRE lock_key 60

舉個(gè)例子(按原文):我們?cè)O(shè)置鎖的過(guò)期時(shí)間是1分鐘。續(xù)約線程可以設(shè)置在50秒的時(shí)候啟動(dòng)檢查。

  • 在第50秒,續(xù)約線程檢查到業(yè)務(wù)還在運(yùn)行,于是重置過(guò)期時(shí)間為1分鐘。
  • 在第100秒(1分40秒),續(xù)約線程再次檢查,業(yè)務(wù)還在,再次重置為1分鐘。
  • 重復(fù)上述檢查重置過(guò)程......
  • 在第130秒(2分10秒),業(yè)務(wù)執(zhí)行完畢,客戶端主動(dòng)釋放鎖。
  • 在第150秒(2分30秒),續(xù)約線程檢查到鎖已被釋放,退出。

理論上,只要確保在剩余過(guò)期時(shí)間內(nèi)能夠續(xù)約成功就可以了。比如這里預(yù)留了10秒(60秒-50秒)的窗口期,就算第一次續(xù)約失敗,也有足夠的時(shí)間進(jìn)行重試。

99

這樣,只要客戶端A沒(méi)有宕機(jī),它就可以在業(yè)務(wù)執(zhí)行期間內(nèi),一直持有這把鎖。

1.2.4 續(xù)約失敗策略

續(xù)約機(jī)制也不是萬(wàn)能的。如果續(xù)約線程在嘗試 EXPIRE 時(shí),因?yàn)榫W(wǎng)絡(luò)問(wèn)題或 Redis 故障而連續(xù)失敗,直到鎖過(guò)期了都沒(méi)成功,該怎么辦?此時(shí),鎖已經(jīng)(或即將)被其他客戶端拿到。原客戶端A的業(yè)務(wù)如果繼續(xù)執(zhí)行,將導(dǎo)致兩個(gè)客戶端同時(shí)處理業(yè)務(wù),破壞了排他性。

這時(shí)我們面臨兩種策略:

  • 保守策略(推薦):續(xù)約失敗意味著鎖的歸屬權(quán)已經(jīng)丟失。業(yè)務(wù)邏輯必須立即中斷并回滾,向上層拋出異常。這是對(duì)數(shù)據(jù)一致性最嚴(yán)格的保障。
  • 激進(jìn)策略:假設(shè)續(xù)約失敗是小概率事件,業(yè)務(wù)邏輯繼續(xù)執(zhí)行。這可能會(huì)導(dǎo)致數(shù)據(jù)不一致,但系統(tǒng)“可用性”更高。此策略慎用。

這里提到了中斷業(yè)務(wù),這又是一個(gè)可以在面試的時(shí)候跟面試官深聊的亮點(diǎn)。

1.2.5 業(yè)務(wù)中斷策略

在分布式鎖出了問(wèn)題(如續(xù)約失敗)時(shí),如何中斷業(yè)務(wù)?這其實(shí)是個(gè)很困難的事情。分布式鎖框架(如 Redisson)并不能直接幫你中斷業(yè)務(wù),它能做的,只是在續(xù)約失敗時(shí),給業(yè)務(wù)代碼發(fā)一個(gè)“中斷信號(hào)”(比如設(shè)置一個(gè) volatile 標(biāo)志位,或者調(diào)用線程的 interrupt() 方法)。是否中斷,以及如何中斷,完全取決于你的業(yè)務(wù)代碼是如何實(shí)現(xiàn)的。

  • 如果你的業(yè)務(wù)是一個(gè)大循環(huán),那么你可以在每個(gè)循環(huán)開(kāi)始的時(shí)候,檢測(cè)一下中斷信號(hào):
// 偽代碼:在循環(huán)中檢測(cè)中斷信號(hào)
for (int i = 0; i < data.size(); i++) {
    // 鎖框架在續(xù)約失敗時(shí),會(huì)設(shè)置一個(gè)中斷標(biāo)志
    if (lock.isInterrupted()) {
        // 中斷業(yè)務(wù),執(zhí)行回滾
        break;
    }
    // 你的業(yè)務(wù)邏輯
    DoSomething(data.get(i));
}
  • 如果你的業(yè)務(wù)沒(méi)有循環(huán),而是由多個(gè)步驟構(gòu)成,那么你可以在每一個(gè)關(guān)鍵步驟之后都檢測(cè)一下:
// 偽代碼:在關(guān)鍵步驟間檢測(cè)中斷信號(hào)
step1();
if (lock.isInterrupted()) { 
    // 中斷并返回
    return; 
}

step2();
if (lock.isInterrupted()) { 
    // 中斷并返回
    return; 
}

step3();

最后可以總結(jié)拔高一下:這種中斷業(yè)務(wù)的難題,在微服務(wù)超時(shí)控制里也會(huì)遇到,目前業(yè)界也沒(méi)有銀彈,它強(qiáng)依賴于業(yè)務(wù)代碼的主動(dòng)配合與檢測(cè)。

2. 釋放鎖

我們前面提到,加鎖時(shí)要用唯一ID作為 value。這個(gè)ID在釋放鎖時(shí)同樣至關(guān)重要。正常來(lái)說(shuō),釋放鎖(DEL)不會(huì)有問(wèn)題。但在一些特殊場(chǎng)景下(比如Redis宕機(jī)恢復(fù),或者業(yè)務(wù)執(zhí)行時(shí)間超過(guò)了鎖過(guò)期時(shí)間),釋放鎖也可能出大問(wèn)題。還設(shè)有這樣一個(gè)場(chǎng)景:

  • 客戶端A加鎖 key1成功(value=vaule1),過(guò)期時(shí)間30秒。
  • 客戶端A遭遇了長(zhǎng)時(shí)間的 Full GC,卡頓了35秒,鎖續(xù)約失效。
  • 在第30秒時(shí),鎖自動(dòng)過(guò)期釋放。
  • 在第31秒時(shí),客戶端B加鎖 key1成功(value=value2)。
  • 在第35秒時(shí),客戶端A從 GC 中蘇醒,它的業(yè)務(wù)邏輯執(zhí)行完畢,發(fā)起 DEL key1。
  • 災(zāi)難發(fā)生:客戶端A“釋放”了客戶端B的鎖。

1010

這個(gè)場(chǎng)景的根源在于,客戶端A釋放了“不屬于自己”的鎖。解決方案就是:釋放鎖時(shí),必須檢查鎖是不是自己的

客戶端在釋放鎖時(shí),不能簡(jiǎn)單粗暴地 DEL。它必須執(zhí)行一個(gè)“復(fù)合操作”:“檢查 value 是否匹配,如果匹配才刪除”。這必須是一個(gè)原子操作,否則在 GET 和 DEL 之間鎖可能又過(guò)期了,產(chǎn)生新的競(jìng)態(tài)條件。

實(shí)現(xiàn)原子化“查刪”的最佳方式是使用 Lua 腳本,因?yàn)?Redis 執(zhí)行 Lua 腳本是原子的。

-- 釋放鎖的 Lua 腳本
-- KEYS[1] 是鎖的 key
-- ARGV[1] 是客戶端的唯一ID (比如 uuid-A)
if redis.call("get", KEYS[1]) == ARGV[1] then
    -- 檢查鎖的 value 是不是自己的 ID,如果是,才刪除
    return redis.call("del", KEYS[1])
else
    -- 如果鎖不存在,或者鎖的 value 不是自己的 ID,則不刪除
    return 0
end

這個(gè)腳本會(huì)先 get 鎖的 value,判斷它是否等于客戶端傳入的唯一ID。如果是,才執(zhí)行 del。這完美解決了誤刪問(wèn)題。

11

3. 高階方案

3.1 Redlock

至此,我們的分布式鎖似乎已經(jīng)很健壯了。但它依然依賴一個(gè)單實(shí)例的 Redis。如果這個(gè) Redis 實(shí)例宕機(jī)了,整個(gè)分布式鎖服務(wù)就癱瘓了。

你可能會(huì)說(shuō):“用 Redis 主從(Master-Slave)復(fù)制和哨兵(Sentinel)來(lái)保證高可用啊!”,但這不行。Redis 的主從復(fù)制是異步的。這會(huì)導(dǎo)致一個(gè)致命缺陷:

  • 客戶端A在 Master 節(jié)點(diǎn)加鎖成功。
  • Master 還沒(méi)來(lái)得及把這個(gè) key 異步復(fù)制給 Slave,就宕機(jī)了。
  • 哨兵(Sentinel)將 Slave 提升為新的 Master。
  • 客戶端B在新 Master 上嘗試加鎖,由于新 Master 上根本沒(méi)有這個(gè) key,B也加鎖成功了。
  • 災(zāi)難發(fā)生:系統(tǒng)中有兩個(gè)客戶端同時(shí)持有了鎖,排他性被打破。

1212

為了解決 Redis 單點(diǎn)和主從異步復(fù)制的缺陷,Redis 的作者 Antirez 提出了 Redlock(紅鎖)算法

Redlock 的思想是“多數(shù)派原則”。它不再依賴單個(gè) Redis 實(shí)例,而是部署 N 個(gè)。當(dāng)大多數(shù)節(jié)點(diǎn)都告訴你加鎖成功的時(shí)候,就說(shuō)明加鎖成功了。比如你同時(shí)在 5 個(gè)節(jié)點(diǎn)上加鎖,那么大多數(shù)就意味著至少 3 個(gè)節(jié)點(diǎn)成功才算加鎖成功。

  • 加鎖流程

客戶端記錄當(dāng)前時(shí)間戳。

依次嘗試在所有5個(gè)實(shí)例上加鎖(使用我們前面完善的 SET ... EX ... NX 命令),并且為每個(gè)實(shí)例設(shè)置一個(gè)很短的連接和響應(yīng)超時(shí)(例如50毫秒),防止在某個(gè)宕機(jī)節(jié)點(diǎn)上浪費(fèi)太多時(shí)間。

統(tǒng)計(jì)加鎖成功的實(shí)例數(shù)量。如果超過(guò)半數(shù)(例如 5個(gè)中的3個(gè))成功,并且總耗時(shí)小于鎖的有效時(shí)間(過(guò)期時(shí)間 - 加鎖耗時(shí)),則認(rèn)為加鎖成功。

鎖的真正有效時(shí)間 = 初始設(shè)置的過(guò)期時(shí)間 - 加鎖總耗時(shí)。

1313

  • 釋放鎖流程

客戶端必須向所有5個(gè)實(shí)例發(fā)起釋放鎖(前面提到的Lua腳本)的操作,無(wú)論加鎖時(shí)該實(shí)例是否成功。這樣做是為了清理可能存在的“僵尸鎖”。

Redlock 通過(guò)“多數(shù)派”機(jī)制,極大地提升了分布式鎖的可用性。即使有1-2個(gè) Redis 實(shí)例宕機(jī),鎖服務(wù)依然可用。

但它也并非銀彈。Redlock 的成本更高,實(shí)現(xiàn)更復(fù)雜,且加鎖的性能開(kāi)銷也更大(需要請(qǐng)求多次)。因此,在實(shí)際選型中,很多公司會(huì)評(píng)估后也會(huì)認(rèn)為這種方案過(guò)于復(fù)雜,單實(shí)例 Redis 帶來(lái)的風(fēng)險(xiǎn)(如宕機(jī)、主從切換)是可接受的,從而選擇繼續(xù)使用我們前面討論的、基于單實(shí)例的健壯方案。

3.2 鎖的性能優(yōu)化

分布式鎖雖然解決了問(wèn)題,但它本身是有開(kāi)銷的:每一次加鎖、續(xù)約、釋放鎖,都是一次(甚至多次)網(wǎng)絡(luò)IO。在高并發(fā)場(chǎng)景下,鎖的競(jìng)爭(zhēng)會(huì)成為性能瓶頸。

其實(shí)分布式鎖能做的優(yōu)化不多。一個(gè)思路是優(yōu)化 Redis本身的性能(比如啟用單獨(dú)的Redis集群,防止被其他業(yè)務(wù)影響),另一個(gè)思路就是減少分布式鎖的競(jìng)爭(zhēng)。

3.2.1 Singleflight 模式

在高并發(fā)下,可能一個(gè)服務(wù)實(shí)例內(nèi)的幾十個(gè)線程,和另外幾十個(gè)實(shí)例的幾百個(gè)線程,都在同一時(shí)刻競(jìng)爭(zhēng)同一把鎖

我們可以借鑒 singleflight 模式(在Go中很常用,Guava中也有類似實(shí)現(xiàn)):針對(duì)同一個(gè) key 的加鎖請(qǐng)求,在單個(gè)實(shí)例內(nèi)部,只允許一個(gè)線程去 Redis 競(jìng)爭(zhēng)分布式鎖。其他線程則在本地等待這個(gè)代表的結(jié)果。

1414

假設(shè)有2個(gè)實(shí)例,每個(gè)實(shí)例上各有10個(gè)線程要去獲得 key1 上的分布式鎖。

  • 無(wú)優(yōu)化:總共有 2 * 10 = 20 個(gè)線程會(huì)涌向 Redis 競(jìng)爭(zhēng)鎖。
  • Singleflight:實(shí)例A內(nèi)部先選出1個(gè)線程,實(shí)例B內(nèi)部也選出1個(gè)線程。最終只有2個(gè)線程去 Redis 競(jìng)爭(zhēng)分布式鎖。

競(jìng)爭(zhēng)壓力驟減,性能顯著提升。競(jìng)爭(zhēng)越激烈,這種方案的效果越好。如果沒(méi)什么并發(fā),那就基本沒(méi)什么效果。

3.2.2 本地鎖交接

這里還有一種更加激進(jìn)的優(yōu)化方案。當(dāng)實(shí)例A的線程T1拿到了分布式鎖并執(zhí)行完業(yè)務(wù)后,它在釋放鎖(DEL)之前,先檢查一下本地(即實(shí)例A的內(nèi)存中)是否還有其他線程(如T2、T3)正在等待這把鎖。

如果有(比如T2在等),T1可以直接在內(nèi)存中把“鎖憑證”(那個(gè)唯一的UUID)轉(zhuǎn)交給T2,并通知T2“你現(xiàn)在可以執(zhí)行了”。T1自己則不去 DEL 鎖,轉(zhuǎn)而由T2在未來(lái)去釋放。

這種本地接力完全省去了一次 DEL 和一次 SETNX 的網(wǎng)絡(luò)開(kāi)銷,在高競(jìng)爭(zhēng)下效果還是非常不錯(cuò)的。

1515

雖然這種方案開(kāi)起來(lái)性能確實(shí)得到了極大的提升,但是實(shí)際生產(chǎn)環(huán)境中這種方式一般用的較少,主要有以下幾個(gè)點(diǎn):

  1. 復(fù)雜度高,容錯(cuò)性差,這種方案引入了本地狀態(tài)依賴(比如本機(jī)的等待隊(duì)列和鎖持有狀態(tài)),一旦實(shí)例 A 崩潰或重啟,這個(gè)“接力關(guān)系”就斷了,但在 Redis 看來(lái)鎖還沒(méi)釋放,造成鎖泄漏
  2. 失去了分布式鎖的原本意義,Redis 分布式鎖設(shè)計(jì)的初衷就是鎖的狀態(tài)由 Redis 統(tǒng)一仲裁,不依賴于任何單節(jié)點(diǎn)的本地狀態(tài)。但這種方法把部分鎖語(yǔ)義搬回了節(jié)點(diǎn)內(nèi)存。這意味著鎖的持有狀態(tài),不再是單一數(shù)據(jù)源,而是 Redis + 本地協(xié)作,這就導(dǎo)致一致性邊界模糊,違背集中鎖仲裁的設(shè)計(jì)哲學(xué)

3.2.3 分布式鎖替換

針對(duì)這種排他場(chǎng)景,還可以進(jìn)一步優(yōu)化。就是不用分布式鎖。分布式鎖是解決并發(fā)的其實(shí)性能消耗還是不小的,如果能換個(gè)思路,也許根本不需要它。嚴(yán)格來(lái)說(shuō),是原本這些場(chǎng)景就不該用分布式鎖。

  1. 數(shù)據(jù)庫(kù)樂(lè)觀鎖

很多場(chǎng)景使用分布式鎖,是為了保護(hù)一個(gè)“讀取數(shù)據(jù) -> 計(jì)算 -> 寫回?cái)?shù)據(jù)”的流程。比如扣減庫(kù)存:SETNX -> SELECT -> 業(yè)務(wù)計(jì)算 -> UPDATE -> DEL。這個(gè)流程完全可以用樂(lè)觀鎖替代。給庫(kù)存表加一個(gè) version 字段:

  • SELECT stock, version FROM inventory WHERE sku_id = 's101'
  • 在內(nèi)存中計(jì)算新庫(kù)存 new_stock = stock - 1
  • UPDATE inventory SET stock = new_stock, version = version + 1 WHERE sku_id = 's101' AND version = (第1步查到的version)

如果 UPDATE 的返回行數(shù)為0,說(shuō)明 version 已被他人修改(并發(fā)沖突)。此時(shí)客戶端只需從第1步開(kāi)始重試即可。全程無(wú)鎖,性能極高。缺點(diǎn)是可能會(huì)有多個(gè)線程在做重復(fù)計(jì)算,但只要最終更新數(shù)據(jù)庫(kù)時(shí)控制住了并發(fā),就沒(méi)關(guān)系。

  1. 一致性哈希負(fù)載均衡

分布式鎖的出現(xiàn),是因?yàn)橥粋€(gè)業(yè)務(wù)請(qǐng)求(如處理訂單 order_id=555)可能被負(fù)載均衡打到任何一個(gè)實(shí)例上。

如果我們能通過(guò)一致性哈希等手段,將特定ID的請(qǐng)求(如按 order_id 哈希)固定路由到同一個(gè)實(shí)例上,那么問(wèn)題就從“分布式”退化成了“單機(jī)”。我們只需要在那個(gè)實(shí)例內(nèi)部使用本地鎖(如 Java 的 ReentrantLock)或者用單機(jī) Singleflight 模式就可以,完全規(guī)避了重量級(jí)的分布式鎖。

1616

4. 小節(jié)

從最初的 SETNX 到完善的續(xù)約、原子化釋放、再到 Redlock 以及各種性能優(yōu)化手段,我們可以看到,分布式鎖的核心始終圍繞兩個(gè)關(guān)鍵詞展開(kāi):可靠性性能。可靠性確保鎖語(yǔ)義不被破壞,性能則決定方案能否真正落地。工程實(shí)踐中,鎖并非萬(wàn)能,很多場(chǎng)景完全可以用樂(lè)觀鎖或一致性哈希等思路替代,從根源上消除“鎖”的需求。掌握這些設(shè)計(jì)背后的取舍邏輯,遠(yuǎn)比死記實(shí)現(xiàn)細(xì)節(jié)更重要。分布式鎖不是終點(diǎn),而是理解分布式一致性與系統(tǒng)取舍的起點(diǎn)。

責(zé)任編輯:武曉燕 來(lái)源: IT楊秀才
相關(guān)推薦

2024-02-28 10:14:47

Redis數(shù)據(jù)硬盤

2024-09-24 16:30:46

分布式鎖Redis數(shù)據(jù)中間件

2022-08-11 18:27:50

面試Redis分布式鎖

2025-07-22 01:33:00

分布式Zookeeper

2025-10-09 01:22:00

2025-03-10 11:48:22

項(xiàng)目服務(wù)設(shè)計(jì)

2022-11-25 17:29:27

分布式事務(wù)

2022-12-08 08:13:11

分布式數(shù)據(jù)庫(kù)CAP

2020-09-27 06:52:22

分布式存儲(chǔ)服務(wù)器

2021-06-03 08:55:54

分布式事務(wù)ACID

2022-06-30 08:04:16

Redis分布式鎖Redisson

2023-08-22 13:16:00

分布式數(shù)據(jù)庫(kù)架構(gòu)數(shù)據(jù)存儲(chǔ)

2022-05-11 13:55:18

高可用性分布式彈性

2025-07-17 00:05:00

MCPAI 技術(shù)Nacos 3.0

2023-11-10 08:44:13

分布式鎖分布式系統(tǒng)

2023-01-12 08:24:45

ZookeeperZK服務(wù)器

2024-07-19 08:14:21

2024-06-26 11:55:44

2021-09-09 08:20:14

Kafka網(wǎng)絡(luò)故障集群

2023-09-01 15:27:31

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日本在线观看不卡| 久久精品91久久久久久再现| ww国产内射精品后入国产| 无码国产色欲xxxx视频| 视频一区欧美精品| 一区二区欧美亚洲| 欧美一级大片免费看| 手机在线理论片| 中文字幕亚洲欧美在线不卡| 国产日韩久久| 国产精品视频中文字幕91| 国产99在线|中文| 亚洲综合成人婷婷小说| 国产性一乱一性一伧一色| 日韩aaa久久蜜桃av| 在线观看欧美日本| 99热这里只有精品免费| 青青视频在线观| 韩日av一区二区| 91av国产在线| 精品国产乱码久久久久久鸭王1| 欧美日韩看看2015永久免费| 欧美精品九九99久久| 日韩av三级在线| 中文字幕伦理免费在线视频| 国产无一区二区| 鬼打鬼之黄金道士1992林正英| jizz国产在线| 亚洲久久成人| 欧美精品在线免费| 成年人免费视频播放| 天堂在线精品| 日韩精品一区二区三区四区视频| 国产又大又黄又粗的视频| av免费不卡国产观看| 中文字幕av一区二区三区高| 久久综合中文色婷婷| 成人h动漫精品一区二区无码 | 日韩精品中文字幕一区| 国产又黄又猛又粗又爽的视频| 欧美日韩经典丝袜| 亚洲乱码国产乱码精品精98午夜 | 久久精品青草| 一区二区三区动漫| 女人又爽又黄免费女仆| 台湾亚洲精品一区二区tv| 欧美成人乱码一区二区三区| 亚洲国产欧美91| 日韩欧美三区| 欧美日韩精品是欧美日韩精品| 粗暴91大变态调教| sis001欧美| 欧美日韩亚洲精品内裤| 内射国产内射夫妻免费频道| ****av在线网毛片| 天天综合色天天综合| 国产69精品久久久久999小说| 麻豆视频在线免费观看| 国产精品久久久久久久久免费相片| 日韩福利在线| h视频在线免费| 国产女主播在线一区二区| 区一区二区三区中文字幕| 久久久久久久影视| 国产欧美精品一区二区三区四区 | 国产欧亚日韩视频| 伊人免费在线观看| 国产一区在线视频| 不卡一区二区三区视频| 蜜桃视频在线观看www| 成人国产精品免费观看| 精品蜜桃一区二区三区| 美女欧美视频在线观看免费| 国产日韩成人精品| 中文字幕中文字幕99| av大片在线| 亚洲成a人片综合在线| 国产精品专区在线| 色豆豆成人网| 5月丁香婷婷综合| 中文字幕视频观看| 要久久爱电视剧全集完整观看 | 国产精品啊v在线| 成年人精品视频| xxxxxx国产| 久久一日本道色综合久久| 国产精品三级网站| www日本在线| 久久久久久久久久久久久久久99 | 免费在线看黄| 亚洲高清视频在线| 啊啊啊国产视频| 日韩一区二区三区高清在线观看| 亚洲精品国产电影| 四虎影视一区二区| 在线播放一区| 国产精品男人的天堂| 亚洲精品国产片| 久久精品亚洲一区二区三区浴池| 手机福利在线视频| 乱人伦视频在线| 欧美日韩国产综合久久 | 91成人一区二区三区| 丁香六月综合激情| 五月天亚洲综合情| 17videosex性欧美| 91 com成人网| 精品国产无码在线观看| 欧美午夜在线| 国产精品亚洲精品| 天天干天天摸天天操| 最新国产精品久久精品| av片中文字幕| 成人性生交大片免费看中文视频| 最近2019中文字幕一页二页| 久草国产精品视频| 国产精品系列在线播放| 神马影院一区二区| 日本三级一区| 日韩你懂的在线观看| 韩国一级黄色录像| 久久狠狠一本精品综合网| 成人av资源网| 黄色在线观看网站| 欧美三级午夜理伦三级中视频| 午夜视频在线观看国产| 牛牛国产精品| 91精品国产自产在线| 国产高清在线看| 欧美丝袜一区二区| 日韩精品视频一区二区| 国产精品v一区二区三区| 91久久久久久| 日本免费视频在线观看| 欧美亚洲丝袜传媒另类| 性久久久久久久久久| 亚洲全部视频| 国产精品乱子乱xxxx| 性欧美高清come| 69av一区二区三区| 国产视频精品免费| 精品一区二区精品| 樱花www成人免费视频| 成人做爰视频www| 亚洲天天在线日亚洲洲精| 国产精品777777| 91老师国产黑色丝袜在线| 777精品久无码人妻蜜桃| 麻豆国产欧美一区二区三区r| 欧美黄色片视频| 亚洲黄色片视频| 亚洲国产色一区| 亚洲自拍偷拍精品| 一本色道久久综合| 狼狼综合久久久久综合网| 九色porny自拍视频在线播放| 亚洲精品电影网| youjizz在线视频| 国产午夜精品福利| jizzzz日本| 婷婷另类小说| 91视频免费在线观看| 久草在线视频网站| 亚洲国产日韩欧美在线图片| 欧美福利视频一区二区| 久久久五月婷婷| 三上悠亚av一区二区三区| 婷婷综合五月| 亚洲最大激情中文字幕| segui88久久综合| 亚洲精品视频在线播放| 成人av网站在线播放| 亚洲欧洲国产日本综合| 天堂va欧美va亚洲va老司机| 99精品视频免费全部在线| 日本高清久久一区二区三区| 欧美成人高清视频在线观看| 欧美精品在线观看91| 五月婷婷伊人网| 欧美三级三级三级| 精品99在线观看| 91美女福利视频| 美女在线视频一区二区| 国产精品草草| 日本一区二区三区四区在线观看| 欧美午夜三级| 国模视频一区二区| 岛国视频免费在线观看| 日韩一区二区三区观看| 一区二区三区福利视频| 国产精品嫩草影院com| 俄罗斯女人裸体性做爰| 国产亚洲午夜| 亚洲最新免费视频| 亚洲人成网站77777在线观看| 国产精品亚洲美女av网站| av福利在线导航| 中文日韩在线观看| 色婷婷视频在线| 欧美日韩国产美| 中文字幕精品三级久久久| 国产精品久久久久久福利一牛影视| 国产av一区二区三区传媒| 免费成人av资源网| 欧美 日韩 国产 高清| 天天精品视频| 色吧亚洲视频| 麻豆精品少妇| 亚洲伊人久久综合| 先锋欧美三级| 69久久夜色精品国产69| 国产福利在线播放麻豆| 亚洲欧美日韩精品久久亚洲区 | 亚洲国产欧美一区| 国产又大又黄又爽| 欧美性生活大片视频| 99久在线精品99re8热| 亚洲免费在线电影| 99在线视频免费| 91丝袜高跟美女视频| 自拍一级黄色片| 久久精品国产免费| 欧美综合在线观看视频| 中文久久精品| 国产日本在线播放| 雨宫琴音一区二区三区| 在线观看欧美一区| av在线不卡免费观看| 欧美激情第一页在线观看| 国产欧美自拍一区| 成人h视频在线观看| 欧美影院视频| 亚洲a成v人在线观看| 日本久久一区| 国产精品午夜一区二区欲梦| 欧美大胆性生话| 欧美在线观看网址综合| 国产三级电影在线播放| 国模极品一区二区三区| 爱情岛亚洲播放路线| 久久久久久久久久亚洲| 欧美aaaxxxx做受视频| 欧美丰满老妇厨房牲生活| 成年人网站在线| 久久亚洲春色中文字幕| 免费观看在线午夜影视| www.久久撸.com| 免费网站免费进入在线| 精品国产欧美成人夜夜嗨| 色影视在线观看| 日韩中文在线不卡| 免费观看在线午夜影视| 久久视频精品在线| 亚洲综合影视| 欧美精品第一页在线播放| 国产精品186在线观看在线播放| 久热精品视频在线| 黄色的视频在线观看| 久久久久久久久久久网站| 美女在线视频免费| 欧美在线中文字幕| yw.尤物在线精品视频| 国产欧美日韩免费| 国产一区二区av在线| 成人区精品一区二区| 麻豆一区二区| 性欧美videosex高清少妇| 久久久久久久久久久久久久| 永久免费看av| 伊人久久成人| 88av.com| 国产综合久久久久影院| 9191在线视频| 91麻豆国产精品久久| 一级特黄曰皮片视频| 亚洲视频免费在线| 国产无遮挡aaa片爽爽| 91国偷自产一区二区三区成为亚洲经典 | 亚洲成人在线观看视频| 免费观看成人毛片| 欧美日韩亚洲综合一区| 亚洲成人精品女人久久久| 日韩av在线看| 日本亚洲精品| 久久免费视频在线观看| 3d欧美精品动漫xxxx无尽| 成人欧美一区二区三区在线| 精品素人av| 亚洲人一区二区| 国内精品久久久久久久影视麻豆| 日韩一级在线免费观看| 国产福利一区二区三区视频在线 | 国产视频第二页| 日韩精品久久久久| 岛国成人毛片| 日本中文字幕不卡免费| 麻豆精品在线| 神马影院我不卡| 在线看片日韩| 天堂av在线8| 91麻豆123| 久热精品在线观看| 欧美日韩高清在线播放| 色呦呦视频在线| 欧美成人高清视频| 日韩欧美一区二区三区在线观看| 不卡视频一区二区| 国产精品国产一区| 国产免费人做人爱午夜视频| 国产999精品久久| 精品在线观看一区| 欧美性生交大片免费| 亚洲精品久久久狠狠狠爱| 色综合伊人色综合网| 日韩欧美一中文字暮专区| www.成人av.com| 色婷婷综合网| 人妻内射一区二区在线视频 | 国产日韩高清一区二区三区在线| 欧美日韩精品区别| 日本一区二区三级电影在线观看| 日本a在线观看| 日韩欧美资源站| 久久77777| 国产女精品视频网站免费| 国产成人三级| 无码aⅴ精品一区二区三区浪潮| 国产iv一区二区三区| 麻豆明星ai换脸视频| 欧美三级韩国三级日本一级| 国产小视频在线| 国内精品久久久久久| avtt综合网| 欧美日韩视频免费| 国产经典欧美精品| 亚洲国产精品久| 制服视频三区第一页精品| 日本免费中文字幕在线| 91精品久久久久久久久| 人人狠狠综合久久亚洲婷婷| 久草在在线视频| 国产欧美一二三区| 无码无套少妇毛多18pxxxx| 亚洲欧美中文日韩v在线观看| 在线高清av| 欧美精品一区二区三区久久| 国产一区二区三区久久| 久久国产精品无码一级毛片| 丁香五六月婷婷久久激情| 天堂成人在线| 欧美孕妇与黑人孕交| 久久99久久人婷婷精品综合| 日韩手机在线观看视频| 久久精品亚洲麻豆av一区二区| 国产女主播喷水视频在线观看 | 免费在线观看成人av| 亚洲av无码一区二区三区人 | 国产精品电影院| 91亚洲国产成人精品一区| 久久中文字幕一区| 精品麻豆剧传媒av国产九九九| 亚洲一区 在线播放| 国产成人午夜精品影院观看视频| 国产一级视频在线观看| 精品亚洲夜色av98在线观看 | 99精品99久久久久久宅男| 伊人久久亚洲美女图片| 亚洲熟妇无码av| 欧美三级三级三级爽爽爽| 在线黄色网页| 黄色小网站91| 日本亚洲最大的色成网站www| 日本黄色录像视频| 亚洲第一福利网站| 偷拍精品精品一区二区三区| 亚洲免费视频一区| 国产成人在线观看免费网站| 久久不卡免费视频| 中文字幕亚洲图片| 99ri日韩精品视频| 成年人视频在线免费| 亚洲老司机在线| 你懂的免费在线观看视频网站| 成人黄色免费片| 夜夜爽av福利精品导航| 精品视频第一页| 亚洲国产欧美日韩精品| 色成人综合网| 日本福利视频在线| 综合色天天鬼久久鬼色| 手机看片国产1024| 成人国产精品一区二区| 国内在线观看一区二区三区| www久久久久久久| 精品捆绑美女sm三区| 国产亚洲一区二区手机在线观看| 国产欧美123| 日本一区二区三区视频视频| 欧美 日韩 国产 成人 在线 91| 国产精品久久久久久久久男|