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