共計(jì) 2835 個(gè)字符,預(yù)計(jì)需要花費(fèi) 8 分鐘才能閱讀完成。
這篇文章主要介紹了 Redis 中 Redlock 的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓丸趣 TV 小編帶著大家一起了解一下。
為什么要用鎖
我待過(guò)的一家 k12 教育公司,我們當(dāng)時(shí)有個(gè)業(yè)務(wù)場(chǎng)景是這樣的。業(yè)務(wù)這邊要給學(xué)生排課,偶爾會(huì)反饋學(xué)生的課時(shí)明明充足的但是卻提示課時(shí)不足,等再刷新一遍頁(yè)面卻發(fā)現(xiàn)學(xué)生的課時(shí)已經(jīng)不夠了。更可怕的是,偶爾會(huì)有學(xué)生的課時(shí)被扣成負(fù)數(shù)(公司被白嫖課時(shí))。
再比如下面這個(gè)例子
上面的這倆個(gè)問(wèn)題都是并發(fā)給我們的業(yè)務(wù)帶來(lái)的問(wèn)題。解決這個(gè)問(wèn)題的核心就是,同一時(shí)間只能允許有一個(gè)請(qǐng)求來(lái)對(duì)這些敏感 (重要) 的數(shù)據(jù)進(jìn)行讀寫(xiě)操作。所以這個(gè)時(shí)候就要使用到分布式鎖來(lái)限制程序的并發(fā)執(zhí)行。
setnx 有哪些問(wèn)題
我們先來(lái)看看用 Redis 如何實(shí)現(xiàn)分布式鎖,想必大家都很熟悉。比如針對(duì)我文章開(kāi)頭講的學(xué)生排課的問(wèn)題我們就可以這樣加鎖
這就是我們常規(guī)使用 setnx 來(lái)實(shí)現(xiàn)鎖的方式。
現(xiàn)在我們假設(shè)有這樣一個(gè)場(chǎng)景。A 請(qǐng)求拿到鎖到了第 2 步給學(xué)生排課的時(shí)候程序掛了,沒(méi)有釋放鎖。那么這個(gè)鎖就成了死鎖,下一個(gè)操作同一個(gè)學(xué)生的請(qǐng)求永遠(yuǎn)就拿不到鎖,那么這個(gè)學(xué)生就沒(méi)法被排課了。這個(gè)時(shí)候都需要手動(dòng)去把鎖釋放掉。
為了解決死鎖的問(wèn)題,我們給鎖加一個(gè)過(guò)期時(shí)間。
加上過(guò)期時(shí)間之后,如果 A 請(qǐng)求沒(méi)有主動(dòng)釋放鎖,在鎖過(guò)期之后也會(huì)主動(dòng)釋放,這樣 B 請(qǐng)求一樣可以獲取鎖處理業(yè)務(wù)邏輯。但是如果在加過(guò)期時(shí)間的時(shí)候也就是在第 1 步和第 2 步之間程序崩潰。那還是會(huì)出現(xiàn)死鎖的問(wèn)題。這個(gè)問(wèn)題的根源就在于 setnx 和 expire 這兩條指令不是原子指令。所以如果 setnx 和 expire 能夠要么全部執(zhí)行要么一個(gè)都不執(zhí)行那該多好。
為此在 Redis2.8 之前社區(qū)涌現(xiàn)了一大批擴(kuò)展包來(lái)解決該問(wèn)題。官方為了治理該亂象,在 2.8 版本中加入了 set 指令的擴(kuò)展參數(shù)使得 setnx 和 expire 指令可以一起執(zhí)行,所以現(xiàn)在我們使用分布式鎖應(yīng)該是這樣了
這樣看起來(lái)已經(jīng)很完美了,已經(jīng)達(dá)成了我們的期望“setnx 和 expire 能夠要么全部執(zhí)行要么一個(gè)都不執(zhí)行那該多好”。我們?cè)偌僭O(shè)現(xiàn)在有如下場(chǎng)景:
A 請(qǐng)求現(xiàn)在獲取到了鎖,鎖的超時(shí)時(shí)間設(shè)置的是 5 秒。到了第 2 步執(zhí)行業(yè)務(wù)邏輯,結(jié)果因?yàn)槟承┰?5 秒之后業(yè)務(wù)邏輯還沒(méi)有執(zhí)行完,此時(shí)鎖由于超時(shí)自動(dòng)釋放了。這個(gè)時(shí)候 B 請(qǐng)求也來(lái)了,拿到鎖之后開(kāi)始執(zhí)行業(yè)務(wù)邏輯。A 請(qǐng)求這個(gè)時(shí)候業(yè)務(wù)邏輯執(zhí)行完了,開(kāi)始執(zhí)行第三步,釋放了鎖。而這個(gè)時(shí)候鎖是 B 請(qǐng)求拿到的,結(jié)果被 A 請(qǐng)求釋放了。那么 C 請(qǐng)求就可以拿到鎖了。這個(gè)時(shí)候 B 請(qǐng)求和 C 請(qǐng)求就會(huì)導(dǎo)致并發(fā)問(wèn)題了。所以可以從這個(gè)例子看出來(lái),在分布式鎖中過(guò)期時(shí)間的設(shè)置非常重要,如果設(shè)置的時(shí)間小于這個(gè)接口的響應(yīng)時(shí)間那么仍然會(huì)產(chǎn)生并發(fā)問(wèn)題。所以我們可以參考接口響應(yīng)時(shí)長(zhǎng)的監(jiān)控來(lái)設(shè)置鎖的過(guò)期時(shí)間。
Redlock
我們上述的方案都是基于單點(diǎn)的 Redis 的實(shí)現(xiàn)方式。單點(diǎn)的 Redis 實(shí)現(xiàn)分布式鎖基本上可以滿足 95% 的業(yè)務(wù)場(chǎng)景。剩下的 5% 就是對(duì)數(shù)據(jù)一致性要求極其嚴(yán)苛并且對(duì)于鎖丟失的 0 容忍的業(yè)務(wù)場(chǎng)景。這個(gè)時(shí)候就得考慮 Redlock 了。至于單點(diǎn)的 Redis 即使通過(guò) sentinel 保證高可用,如果這個(gè) master 節(jié)點(diǎn)由于某些原因發(fā)生了主從切換,如果數(shù)據(jù)主從數(shù)據(jù)同步不及時(shí)那么勢(shì)必會(huì)有數(shù)據(jù)丟失,那么就會(huì)出現(xiàn)鎖丟失的情況。
假設(shè)存在多個(gè) Redis 實(shí)例,這些節(jié)點(diǎn)是完全獨(dú)立的,不需要使用復(fù)制或者任何協(xié)調(diào)數(shù)據(jù)的系統(tǒng),我們假設(shè)有 5 個(gè) Redis master 節(jié)點(diǎn),客戶端為了取到鎖,步驟將會(huì)變成這樣:
以毫秒為單位獲取當(dāng)前的服務(wù)器時(shí)間
嘗試使用相同的 key 和隨機(jī)值來(lái)獲取鎖,客戶端對(duì)每一個(gè)機(jī)器獲取鎖時(shí)都應(yīng)該有一個(gè)超時(shí)時(shí)間,比如鎖的過(guò)期時(shí)間為 10s,那么獲取單個(gè)節(jié)點(diǎn)鎖的超時(shí)時(shí)間就應(yīng)該為 5 到 50 毫秒左右,他這樣做的目的是為了保證客戶端與故障的機(jī)器連接不耗費(fèi)多余的時(shí)間!超時(shí)間時(shí)間內(nèi)未獲取數(shù)據(jù)就放棄該節(jié)點(diǎn),從而去下一個(gè) Redis 節(jié)點(diǎn)獲取。
獲取完成后,獲取當(dāng)前時(shí)間減去步驟一獲取的時(shí)間,當(dāng)且僅當(dāng)客戶端從半數(shù)以上 (這里是 3 個(gè)節(jié)點(diǎn)) 的 Redis 節(jié)點(diǎn)獲取到鎖且獲取鎖的時(shí)間小于鎖額超時(shí)時(shí)間,則證明該鎖生效!
如果取到了鎖,key 的真正有效時(shí)間等于有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟 3 計(jì)算的結(jié)果)。
如果獲取鎖的機(jī)器不滿足半數(shù)以上,或者鎖的超時(shí)時(shí)間計(jì)算完畢后為負(fù)數(shù)等異常操作,則系統(tǒng)會(huì)嘗試解鎖所有實(shí)例,即便某些 Redis 實(shí)例根本就沒(méi)有加鎖成功,防止某些節(jié)點(diǎn)獲取到鎖但是客戶端沒(méi)有得到響應(yīng)而導(dǎo)致接下來(lái)的一段時(shí)間不能被重新獲取鎖
所以我們看出,redlock 其實(shí)是比單點(diǎn) Redis 看起來(lái)更加可靠的鎖。
如果你跟我一樣是 Node.js 程序員那么正好有第三方庫(kù) redlock 直接使用。
我們真的需要 redlock 嗎
關(guān)于 redlock 其實(shí)也有另外一種聲音,Martin Kleppmann(劍橋大學(xué)的研究員,從事數(shù)據(jù)庫(kù)、分布式系統(tǒng)和信息安全交叉領(lǐng)域的 TRVE DATA 項(xiàng)目)寫(xiě)過(guò)一篇 blog 發(fā)表了關(guān)于對(duì) redlock 的一些看法,感興趣的可以看看。Redis 作者 Salvatore 也對(duì)這篇文章的疑問(wèn)做出了一些回應(yīng),還挺有意思的。作者的 blog 主要的觀點(diǎn)如下:
分布式鎖的用途無(wú)非兩種:
效率:使用鎖可以避免不必要地做同樣的工作兩次(例如一些昂貴的計(jì)算)。如果鎖定失敗并且兩個(gè)節(jié)點(diǎn)最終完成相同的工作,結(jié)果是成本略有增加(您最終向 AWS 支付的費(fèi)用比其他情況多 5 美分)或帶來(lái)輕微的不便(例如,用戶最終兩次收到相同的電子郵件通知)。
正確性:使用鎖可以防止并發(fā)進(jìn)程相互干擾并破壞系統(tǒng)狀態(tài)。如果鎖定失敗并且兩個(gè)節(jié)點(diǎn)同時(shí)處理同一條數(shù)據(jù),則結(jié)果是文件損壞、數(shù)據(jù)丟失、永久性不一致、給患者服用的藥物劑量錯(cuò)誤或其他一些非常嚴(yán)重的問(wèn)題。
如果是為了效率,則根本沒(méi)有必要承擔(dān) Redlock 的成本和復(fù)雜性,鎖丟失導(dǎo)致多發(fā)幾次郵件和運(yùn)行 5 個(gè) Redis 服務(wù)器的成本相比,最好只使用單個(gè) Redis 實(shí)例。如果你使用的是單個(gè) Redis 實(shí)例,Redis 節(jié)點(diǎn)突然斷電或者崩潰,或者出現(xiàn)其他問(wèn)題,這個(gè)時(shí)候當(dāng)然會(huì)丟失鎖。但是如果你只是將鎖用作效率優(yōu)化,而且這種崩潰不會(huì)經(jīng)常發(fā)生,那沒(méi)什么大不了的。這種“沒(méi)什么大不了”的場(chǎng)景是 也恰好是 Redis 優(yōu)秀的地方。至少如果依賴單個(gè) Redis 實(shí)例,那么查看系統(tǒng)的每個(gè)人都能夠更方便的定位問(wèn)題。
如果是為了正確性,那么嚴(yán)格來(lái)講,redlock 根本不具有強(qiáng)一致的嚴(yán)格性。舉了一些例子
時(shí)序和系統(tǒng)時(shí)鐘做出了危險(xiǎn)的假設(shè),對(duì)每臺(tái)服務(wù)器的時(shí)鐘強(qiáng)依賴。因?yàn)橛邢到y(tǒng)有 GC 的存在,做 GC 的時(shí)整個(gè)服務(wù)器是夯住的,時(shí)間也就停滯了,所以我們不能夠?qū)r(shí)鐘有強(qiáng)依賴。
沒(méi)有令牌。客戶端每次獲取鎖的時(shí)候服務(wù)端沒(méi)有下發(fā)令牌,服務(wù)端應(yīng)該校驗(yàn)每次操作的時(shí)候客戶端的令牌要與服務(wù)端當(dāng)前的令牌一致才難操作鎖。
感謝你能夠認(rèn)真閱讀完這篇文章,希望丸趣 TV 小編分享的“Redis 中 Redlock 的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持丸趣 TV,關(guān)注丸趣 TV 行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!