redis分布式锁实现原理_redis分布式锁实现分析与实践
前言: 在分布式環(huán)境中, 我們有些情況下需要使用到鎖進(jìn)行并發(fā)控制, 可供基于的 redis, zookeeper,mysql類數(shù)據(jù)庫(kù) 基于數(shù)據(jù)庫(kù)類的實(shí)現(xiàn)是樂(lè)觀鎖, 基于redis,zookeeper的實(shí)現(xiàn)可以認(rèn)為是悲觀鎖.. 樂(lè)觀鎖與悲觀鎖最根本的區(qū)別是在于線程之間是否相互阻塞.
背景:一個(gè)訂單,客戶正在前臺(tái)修改地址,管理員在后臺(tái)同時(shí)修改備注。地址和備注字段的修改,都必須正確更新,這二個(gè)請(qǐng)求同時(shí)到達(dá)的話,如果不借助db的事務(wù),很容易造成行鎖競(jìng)爭(zhēng),但用事務(wù)的話,db的性能顯然比不上redis輕量。
解決思路:A,B二個(gè)請(qǐng)求,誰(shuí)先搶到分布式鎖(假設(shè)A先搶到鎖),誰(shuí)先處理,搶不到的那個(gè)(即:B),在一旁不停等待重試,重試期間一旦發(fā)現(xiàn)獲取鎖成功,即表示A已經(jīng)處理完,把鎖釋放了。這時(shí)B就可以繼續(xù)處理了。
但有二點(diǎn)要注意:
a、需要設(shè)置等待重試的最長(zhǎng)時(shí)間,否則如果A處理過(guò)程中有bug,一直卡死, 或宕機(jī),或者未能正確釋放鎖,B就一直會(huì)等待重試,但是又永遠(yuǎn)拿不到鎖。
b、等待最長(zhǎng)時(shí)間,必須小于鎖的過(guò)期時(shí)間。否則,假設(shè)鎖2秒過(guò)期自動(dòng)釋放,但是A還沒(méi)處理完(即:A的處理時(shí)間大于2秒),這時(shí)鎖會(huì)因?yàn)閞edis key過(guò)期“提前”誤釋放,B重試時(shí)拿到鎖,造成A,B同時(shí)處理。(注:可能有同學(xué)會(huì)說(shuō),不設(shè)置鎖的過(guò)期時(shí)間,不就完了么?理論上講,確實(shí)可以這么做,但是如果業(yè)務(wù)代碼有bug,導(dǎo)致處理完后沒(méi)有unlock,或者根本忘記了unlock,分布式鎖就會(huì)一直無(wú)法釋放。所以綜合考慮,給分布式鎖加一個(gè)“保底”的過(guò)期時(shí)間,讓其始終有機(jī)會(huì)自動(dòng)釋放,更為靠譜)
那在設(shè)計(jì)上到底如何實(shí)現(xiàn)呢,直接上可用的:
從redis2.6.12版開(kāi)始, redis為set命令增加(set [key] NX/XX EX/PX [expiration]);
- EX seconds – 設(shè)置鍵key的過(guò)期時(shí)間,單位時(shí)秒
- PX milliseconds – 設(shè)置鍵key的過(guò)期時(shí)間,單位時(shí)毫秒
- NX – 只有鍵key不存在的時(shí)候才會(huì)設(shè)置key的值
- XX – 只有鍵key存在的時(shí)候才會(huì)設(shè)置key的值
set mylock nx ex 10 加鎖
為了防止死鎖,redis至少設(shè)置一個(gè)過(guò)期時(shí)間,: 一旦設(shè)置過(guò)期時(shí)間就引申出來(lái)以下情況: - 當(dāng)鎖自動(dòng)釋放了,但是程序并沒(méi)有執(zhí)行完畢這個(gè)做發(fā)有二種: 1)加鎖與釋放鎖控制中間邏輯, 使其在可控時(shí)間內(nèi)執(zhí)行完(這個(gè)有些廢話了).2)在還沒(méi)有釋放的時(shí)候,給這個(gè)鎖續(xù)上時(shí)間, 比如這次枷鎖需要執(zhí)行20分鐘, 過(guò)期時(shí)間設(shè)置的10秒, 那就可以使用一個(gè)線程每過(guò)5秒檢測(cè)一下這個(gè)鎖是否被我這個(gè)進(jìn)程枷鎖,如果是并且檢測(cè)出來(lái)過(guò)期時(shí)間小于5秒, 給這個(gè)鎖續(xù)上10秒. 那引發(fā)了一個(gè)問(wèn)題:怎么知道這個(gè)鎖是否被本進(jìn)程鎖著呢,
這時(shí)候value值派上用場(chǎng)了 set mylock my+threadID+隨機(jī)數(shù) nx ex 10 我在這里value使用 serverid + 進(jìn)程ID+ 隨機(jī)數(shù) 這樣確保唯一性, 光使用進(jìn)程ID會(huì)重的的
這樣做的好處在于:
1) 每個(gè)獲取redis鎖的線程應(yīng)該釋放自己獲取到的鎖,而不是其他線程的.
2) 在釋放鎖的時(shí)候需要判斷當(dāng)前鎖是否屬于自己,如果屬于自己才釋放,這里涉及到邏輯判斷語(yǔ)句,至少是兩個(gè)操作在進(jìn)行,那么我們需要考慮這兩個(gè)操作要在一個(gè)原子內(nèi)執(zhí)行,否者在兩個(gè)行為之間可能會(huì)有其他線程插入執(zhí)行,導(dǎo)致程序紊亂
如果你這樣做基本就可以滿足一般的需求了.
我們來(lái)分析一下:redlock
- 更可靠的鎖; 單實(shí)例的redis(這里指只有一個(gè)master節(jié)點(diǎn))往往是不可靠的,雖然實(shí)現(xiàn)起來(lái)相對(duì)簡(jiǎn)單一些,但是會(huì)面臨著宕機(jī)等不可用的場(chǎng)景,即使在主從復(fù)制的時(shí)候也顯得并不可靠(因?yàn)閞edis的主從復(fù)制往往是異步的)。 文章分析得出,這種算法只需具備3個(gè)特性就可以實(shí)現(xiàn)一個(gè)最低保障的分布式鎖。
- 安全屬性(Safety property): 獨(dú)享(相互排斥)。在任意一個(gè)時(shí)刻,只有一個(gè)客戶端持有鎖。
- 活性A(Liveness property A): 無(wú)死鎖。即便持有鎖的客戶端崩潰(crashed)或者網(wǎng)絡(luò)被分裂(gets partitioned),鎖仍然可以被獲取。
- 活性B(Liveness property B): 容錯(cuò)。 只要大部分Redis節(jié)點(diǎn)都活著,客戶端就可以獲取和釋放鎖.
第一點(diǎn)安全屬性意味著悲觀鎖(互斥鎖)是我們做redis分布式鎖的前提,否者將可能造成并發(fā);
第二點(diǎn)表明為了避免死鎖,我們需要設(shè)置鎖超時(shí)時(shí)間,保證在一定的時(shí)間過(guò)后,鎖可以重新被利用;
第三點(diǎn)是說(shuō)對(duì)于客戶端來(lái)說(shuō),獲取鎖和手動(dòng)釋放鎖可以有更高的可靠性。
更進(jìn)一步分析,結(jié)合上文提到的關(guān)鍵問(wèn)題,這里可以引申出另外的兩個(gè)問(wèn)題:
- 怎么才能合理判斷程序真正處理的有效時(shí)間范圍?(這里有個(gè)時(shí)間偏移的問(wèn)題)
- redis Master節(jié)點(diǎn)宕機(jī)后恢復(fù)(可能還沒(méi)有持久化到磁盤)、主從節(jié)點(diǎn)切換,(N/2)+1這里的N應(yīng)該怎么動(dòng)態(tài)計(jì)算更合理?
接下來(lái)再看,redis作者對(duì)redlock的解說(shuō):
總結(jié)
以上是生活随笔為你收集整理的redis分布式锁实现原理_redis分布式锁实现分析与实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: bootstrap项目实例_Spring
- 下一篇: eslint git提交不上_Git常用