Redis分布式鎖一定要避開的兩個(gè)坑
1 第一個(gè)坑:錯(cuò)誤釋放鎖時(shí)機(jī)
1.1. 發(fā)現(xiàn)問題
分析以下代碼存在什么問題:
// 分布式鎖服務(wù)
public interface RedisLockService {
// 獲取鎖
public boolean getLock(String key);
// 釋放鎖
public boolean releaseLock(String key);
}
// 業(yè)務(wù)服務(wù)
public class BizService {
@Resource
private RedisLockService redisLockService;
public void bizMethod(String bizId) {
try {
// 獲取鎖
if(redisLockService.getLock(bizId)) {
// 業(yè)務(wù)重復(fù)校驗(yàn)
if(!bizValidate(bizId)) {
throw new BizException(ErrorBizCode.REPEATED);
}
// 執(zhí)行業(yè)務(wù)
return doBusiness();
}
// 獲取鎖失敗
throw new BizException(ErrorBizCode.GET_LOCK_ERROR);
} finally {
// 釋放鎖
redisLockService.releaseLock(bizId);
}
}
}上述代碼看似沒問題,實(shí)則隱藏大問題。問題在于釋放鎖時(shí)沒有校驗(yàn)當(dāng)前線程是否拿到鎖:
- 線程1和線程2同一時(shí)刻訪問業(yè)務(wù)方法
- 線程2獲取鎖成功,進(jìn)行業(yè)務(wù)處理
- 線程1沒有獲取到鎖,但是釋放鎖成功
- 此時(shí)有線程3嘗試獲取鎖成功,但是線程2業(yè)務(wù)沒有處理完,所以線程3不會(huì)導(dǎo)致業(yè)務(wù)重復(fù)異常
- 最終導(dǎo)致線程2和線程3重復(fù)執(zhí)行業(yè)務(wù)
1.2 解決問題
解決方案是在確認(rèn)獲取鎖成功后才允許釋放鎖:
public class BizService {
@Resource
private RedisLockService redisLockService;
public void bizMethod(String bizId) {
boolean getLockSuccess = false;
try {
// 嘗試獲取鎖
getLockSuccess = redisLockService.getLock(bizId);
// 獲取鎖成功
if(getLockSuccess) {
// 業(yè)務(wù)重復(fù)校驗(yàn)
if(!bizValidate(bizId)) {
throw new BizException(ErrorBizCode.REPEATED);
}
// 執(zhí)行業(yè)務(wù)
return doBusiness();
}
// 獲取鎖失敗
throw new BizException(ErrorBizCode.GET_LOCK_ERROR);
} finally {
// 獲取鎖成功才允許釋放鎖
if(getLockSuccess) {
redisLockService.releaseLock(bizId);
}
}
}
}2 第二個(gè)坑:緩存失效問題
第二個(gè)問題是Redis還存在內(nèi)存清理機(jī)制,可能會(huì)導(dǎo)致分布式鎖失效。
2.1 過(guò)期清理機(jī)制
(1) 定期刪除
Redis定時(shí)檢查哪些key已經(jīng)過(guò)期,發(fā)現(xiàn)過(guò)期則刪除
(2) 惰性刪除
如果key非常多,定期刪除會(huì)非常消耗資源,所以引入惰性刪除策略
如果Redis訪問key時(shí)發(fā)現(xiàn)已經(jīng)過(guò)期則直接刪除
2.2 內(nèi)存回收機(jī)制
當(dāng)內(nèi)存不足時(shí)Redis會(huì)選擇一些元素進(jìn)行刪除:
no-enviction
禁止驅(qū)逐數(shù)據(jù),新寫入操作會(huì)報(bào)錯(cuò)
volatile-lru
從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集選擇最近最少使用的數(shù)據(jù)淘汰
volatile-ttl
從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集選擇將要過(guò)期的數(shù)據(jù)淘汰
volatile-random
從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集選擇任意的數(shù)據(jù)淘汰
allkeys-lru
從數(shù)據(jù)集選擇最近最少使用的數(shù)據(jù)淘汰
allkeys-random
從數(shù)據(jù)集選擇任意的數(shù)據(jù)淘汰
至少存在兩種場(chǎng)景導(dǎo)致分布式鎖失效問題:
- 場(chǎng)景一:Redis內(nèi)存不足進(jìn)行內(nèi)存回收,使用
allkeys-lru或者allkeys-random回收策略導(dǎo)致鎖失效 - 場(chǎng)景二:線程獲取分布式鎖成功,但處理業(yè)務(wù)時(shí)間過(guò)長(zhǎng),此時(shí)鎖到期被定時(shí)清理,導(dǎo)致其它線程獲取鎖成功并重復(fù)執(zhí)行業(yè)務(wù)
2.3 樂觀鎖
通用方案是在數(shù)據(jù)庫(kù)層保護(hù),例如庫(kù)存扣減業(yè)務(wù)在數(shù)據(jù)庫(kù)層用樂觀鎖,原理參看《MySQL樂觀鎖扣減庫(kù)存原理圖解》這篇文章。
udpate goods set stock = stock - #{acquire}
where sku_id = #{skuId} and stock - #{acquire} >= 0到此這篇關(guān)于Redis分布式鎖一定要避開的兩個(gè)坑的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入解析Redis的LRU與LFU算法實(shí)現(xiàn)
這篇文章主要重點(diǎn)介紹了Redis的LRU與LFU算法實(shí)現(xiàn),并分析總結(jié)了兩種算法的實(shí)現(xiàn)效果以及存在的問題,并闡述其優(yōu)劣特性,感興趣的小伙伴跟著小編一起來(lái)看看吧2023-07-07
Redis集群水平擴(kuò)展、集群中添加以及刪除節(jié)點(diǎn)的操作
這篇文章主要介紹了Redis集群水平擴(kuò)展、集群中添加以及刪除節(jié)點(diǎn)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03
redis做websocket分布式消息推送服務(wù)的實(shí)現(xiàn)
本文介紹了使用Redis作為消息隊(duì)列實(shí)現(xiàn)WebSocket分布式消息推送服務(wù)的方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Redis 2.8-4.0過(guò)期鍵優(yōu)化過(guò)程全紀(jì)錄
這篇文章主要給大家介紹了關(guān)于Redis 2.8-4.0過(guò)期鍵優(yōu)化的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Redis實(shí)現(xiàn)主從復(fù)制方式(Master&Slave)
這篇文章主要介紹了Redis實(shí)現(xiàn)主從復(fù)制方式(Master&Slave),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
Redis使用ZSET實(shí)現(xiàn)消息隊(duì)列的項(xiàng)目實(shí)踐
本文主要介紹了Redis使用ZSET實(shí)現(xiàn)消息隊(duì)列的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Redis的數(shù)據(jù)存儲(chǔ)及String類型的實(shí)現(xiàn)
這篇文章主要介紹了Redis的數(shù)據(jù)存儲(chǔ)及String類型的實(shí)現(xiàn),redis作為k-v數(shù)據(jù)存儲(chǔ),因查找和操作的時(shí)間復(fù)雜度都是O(1)和豐富的數(shù)據(jù)類型及數(shù)據(jù)結(jié)構(gòu)的優(yōu)化,了解了這些數(shù)據(jù)類型和結(jié)構(gòu)更有利于我們平時(shí)對(duì)于redis的使用,需要的朋友可以參考下2022-10-10
Redis實(shí)現(xiàn)分布式隊(duì)列淺析
Redis將數(shù)據(jù)存儲(chǔ)在內(nèi)存中,使得讀寫速度非??欤?jīng)常被用來(lái)做緩存系統(tǒng),這里我們將redis用來(lái)做一個(gè)分布式的消息隊(duì)列。這篇文章主要介紹了使用redis來(lái)作為消息隊(duì)列,并且進(jìn)行分布式主從配置,有需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2016-11-11

