redis和redisson實(shí)現(xiàn)分布式鎖的操作方法
基于setnx命令的分布式鎖
1. 加鎖
使用 Redis 實(shí)現(xiàn)分布式鎖,最直接的想法是利用 setnx 和 expire 命令實(shí)現(xiàn)加鎖。
在 Redis 中,setnx 是「set if not exists」如果不存在,則 setnx 的意思,當(dāng)一個線程執(zhí)行 setnx 返回 1,說明 key 不存在,該線程獲得鎖;當(dāng)一個線程執(zhí)行 setnx 返回 0,說明 key 已經(jīng)存在,那么獲取鎖失敗。
SETNX lockKey uniqueValue (integer) 1 SETNX lockKey uniqueValue (integer) 0
2. 釋放鎖
釋放鎖的話,直接通過 DEL 命令刪除對應(yīng)的 key 即可。
DEL lockKey (integer) 1
為了防止誤刪到其他的鎖,這里我們建議使用 Lua 腳本通過 key 對應(yīng)的 value(唯一值)來判斷。選用 Lua 腳本是為了保證解鎖操作的原子性。因?yàn)?Redis 在執(zhí)行 Lua 腳本時(shí),可以以原子性的方式執(zhí)行,從而保證了鎖釋放操作的原子性。
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0
釋放鎖時(shí),先比較鎖對應(yīng)的 value 值是否相等,value值可以在加鎖的時(shí)候把當(dāng)前的線程 ID 當(dāng)做value,并在刪除之前驗(yàn)證 key 對應(yīng)的 value 是不是自己線程的 ID,避免鎖的誤釋放
3. setnx缺點(diǎn)
setnx 的 key 必須設(shè)置一個超時(shí)時(shí)間,以保證即使沒有被顯式釋放,這把鎖也要在一定時(shí)間后自動釋放??梢允褂胑xpire命令設(shè)置鎖超時(shí)時(shí)間
4. 存在問題:
setnx 和 expire 不是原子性的操作,假設(shè)某個線程執(zhí)行setnx 命令,成功獲得了鎖,但是還沒來得及執(zhí)行expire 命令,服務(wù)器就掛掉了,這樣一來,這把鎖就沒有設(shè)置過期時(shí)間了,變成了死鎖,別的線程再也沒有辦
法獲得鎖了。
解決方案:redis的set命令支持在獲取鎖的同時(shí)設(shè)置key的過期時(shí)間
基于set命令的分布式鎖
Redis 從 2.6.12 起,SET 涵蓋了 SETEX 的功能,并且 SET 本身已經(jīng)包含了設(shè)置過期時(shí)間的功能
使用set命令加鎖并設(shè)置鎖過期時(shí)間:
127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX OK
lockKey:加鎖的鎖名;
uniqueValue:能夠唯一標(biāo)示鎖的隨機(jī)字符串;
NX:只有當(dāng) lockKey 對應(yīng)的 key 值不存在的時(shí)候才能 SET 成功;
EX:過期時(shí)間設(shè)置(秒為單位)EX 3 標(biāo)示這個鎖有一個 3 秒的自動過期時(shí)間。與 EX 對應(yīng)的是 PX(毫秒為單位),這兩個都是過期時(shí)間設(shè)置。
一定要保證設(shè)置指定 key 的值和過期時(shí)間是一個原子操作?。?! 不然的話,依然可能會出現(xiàn)鎖無法被釋放的問題。
不過,這種解決辦法同樣存在漏洞:如果操作共享資源的時(shí)間大于過期時(shí)間,就會出現(xiàn)鎖提前過期的問題,進(jìn)而導(dǎo)致分布式鎖直接失效。如果鎖的超時(shí)時(shí)間設(shè)置過長,又會影響到性能。
為了解決這個問題,我們可以讓獲得鎖的線程開啟一個守護(hù)線程,用來給快要過期的鎖“續(xù)期”。在JAVA的Redisson包中有一個”看門狗”機(jī)制,已經(jīng)幫我們實(shí)現(xiàn)了這個功能。
redission看門狗分布式鎖
Redisson 中的分布式鎖自帶自動續(xù)期機(jī)制,使用起來非常簡單,原理也比較簡單,其提供了一個專門用來監(jiān)控和續(xù)期鎖的 Watch Dog( 看門狗),如果操作共享資源的線程還未執(zhí)行完成的話,Watch Dog 會不斷地延長鎖的過期時(shí)間,進(jìn)而保證鎖不會因?yàn)槌瑫r(shí)而被釋放。
1.加鎖機(jī)制
- 線程去獲取鎖,獲取成功:執(zhí)行l(wèi)ua腳本,保存數(shù)據(jù)到redis數(shù)據(jù)庫。
- 此時(shí)另外一個線程去獲取鎖,可以一直通過while循環(huán)嘗試獲取鎖(鎖重試),
如果在獲取鎖的最大等待時(shí)間內(nèi)加鎖成功,執(zhí)行l(wèi)ua腳本,保存數(shù)據(jù)到redis數(shù)據(jù)庫。如果失敗,則返回加鎖失敗。
2.watch dog自動延期機(jī)制:
Redisson在獲取鎖之后,會維護(hù)一個看門狗線程,在每一個鎖設(shè)置的過期時(shí)間的1/3處,如果線程還沒執(zhí)行完任務(wù),則不斷延長鎖的有效期。剛開始鎖的過期時(shí)間默認(rèn)是30秒,可以通過 lockWactchdogTimeout 參數(shù)來改變。
每過 10 秒,看門狗就會執(zhí)行續(xù)期操作,將鎖的超時(shí)時(shí)間重置為 30 秒
。看門狗續(xù)期前也會先判斷是否需要執(zhí)行續(xù)期操作,需要才會執(zhí)行續(xù)期,否則取消續(xù)期操作。
看門狗啟動后,對整體性能也會有一定影響,默認(rèn)情況下看門狗線程是不啟動的。如果使用redisson進(jìn)行加鎖的同時(shí)設(shè)置了鎖的過期時(shí)間,也會導(dǎo)致看門狗機(jī)制失效。
加鎖的時(shí)間默認(rèn)是30秒,如果加鎖的業(yè)務(wù)沒有執(zhí)行完,那么每隔 30 ÷ 3 = 10秒,就會進(jìn)行一次續(xù)期,把鎖重置成30秒,保證解鎖前鎖不會自動失效。
3.redisson分布式鎖的關(guān)鍵點(diǎn):
- 對key不設(shè)置過期時(shí)間,由Redisson在加鎖成功后給維護(hù)一個watchdog看門狗,watchdog負(fù)責(zé)定時(shí)監(jiān)聽并處理,在鎖沒有被釋放且快要過期的時(shí)候自動對鎖進(jìn)行續(xù)期,保證解鎖前鎖不會自動失效
- 通過Lua腳本實(shí)現(xiàn)了加鎖和解鎖的原子操作,底層是使用setnx和lua腳本
- 通過記錄獲取鎖的客戶端id,每次加鎖時(shí)判斷是否是當(dāng)前客戶端已經(jīng)獲得鎖,實(shí)現(xiàn)了可重入鎖。
Redisson 的分布式可重入鎖 RLock
為例來說明如何使用 Redisson 實(shí)現(xiàn)分布式鎖:
public String testLock() throws InterruptedException { RLock lock = redissonClient.getLock("it_lock"); //嘗試獲取鎖,tryLock參數(shù)分別是:獲取鎖的最大等待時(shí)間(期間重試) ,鎖自動釋放時(shí)間,時(shí)間單位 //lock.tryLock(10, 30,TimeUnit.SECONDS); //設(shè)置鎖釋放時(shí)間 不會續(xù)期操作 boolean isLock = lock.tryLock(10, TimeUnit.SECONDS); //沒有設(shè)置鎖釋放時(shí)間 守護(hù)線程會自動續(xù)期 //是否成功 if(isLock){ try { //業(yè)務(wù)邏輯 }finally { lock.unlock(); } } return "finish"; }
指定鎖超時(shí)時(shí)間,不會使用自動續(xù)期機(jī)制
lock.tryLock(10, 30,TimeUnit.SECONDS); //設(shè)置鎖釋放時(shí)間 不會續(xù)期操作
只有未指定鎖超時(shí)時(shí)間,才會使用到 Watch Dog 自動續(xù)期機(jī)制。
// 手動給鎖設(shè)置過期時(shí)間,不具備 Watch Dog 自動續(xù)期機(jī)制 lock.tryLock(10, TimeUnit.SECONDS);
如果使用 Redis 來實(shí)現(xiàn)分布式鎖的話,還是比較推薦直接基于 Redisson 來做的
到此這篇關(guān)于redis和redisson實(shí)現(xiàn)分布式鎖的文章就介紹到這了,更多相關(guān)redis和redisson實(shí)現(xiàn)分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis優(yōu)惠券秒殺企業(yè)實(shí)戰(zhàn)
本文主要介紹了Redis優(yōu)惠券秒殺企業(yè)實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07詳解用Redis實(shí)現(xiàn)Session功能
本篇文章主要介紹了用Redis實(shí)現(xiàn)Session功能,具有一定的參考價(jià)值,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。2016-12-12redis中token失效引發(fā)的一次生產(chǎn)事故
項(xiàng)目再測試的時(shí)候發(fā)現(xiàn)不定時(shí)token失效,本文主要介紹了redis中token失效引發(fā)的一次生產(chǎn)事故,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-03-03