分析以下代碼存在什么問(wè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); } } }
上述代碼看似沒(méi)問(wèn)題,實(shí)則隱藏大問(wèn)題。問(wèn)題在于釋放鎖時(shí)沒(méi)有校驗(yàn)當(dāng)前線程是否拿到鎖:
線程1和線程2同一時(shí)刻訪問(wèn)業(yè)務(wù)方法
線程2獲取鎖成功,進(jìn)行業(yè)務(wù)處理
線程1沒(méi)有獲取到鎖,但是釋放鎖成功
此時(shí)有線程3嘗試獲取鎖成功,但是線程2業(yè)務(wù)沒(méi)有處理完,所以線程3不會(huì)導(dǎo)致業(yè)務(wù)重復(fù)異常
最終導(dǎo)致線程2和線程3重復(fù)執(zhí)行業(yè)務(wù)
解決方案是在確認(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); } } } }
第二個(gè)問(wèn)題是Redis還存在內(nèi)存清理機(jī)制,可能會(huì)導(dǎo)致分布式鎖失效。
(1) 定期刪除
Redis定時(shí)檢查哪些key已經(jīng)過(guò)期,發(fā)現(xiàn)過(guò)期則刪除
(2) 惰性刪除
如果key非常多,定期刪除會(huì)非常消耗資源,所以引入惰性刪除策略
如果Redis訪問(wèn)key時(shí)發(fā)現(xiàn)已經(jīng)過(guò)期則直接刪除
當(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)致分布式鎖失效問(wèn)題:
場(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ù)
通用方案是在數(shù)據(jù)庫(kù)層保護(hù),例如庫(kù)存扣減業(yè)務(wù)在數(shù)據(jù)庫(kù)層用樂(lè)觀鎖。
udpate goods set stock = stock - #{acquire} where sku_id = #{skuId} and stock - #{acquire} >= 0
以上就是Redis分布式鎖一定要避開(kāi)的兩個(gè)坑是什么的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!
每個(gè)人都需要一臺(tái)速度更快、更穩(wěn)定的 PC。隨著時(shí)間的推移,垃圾文件、舊注冊(cè)表數(shù)據(jù)和不必要的后臺(tái)進(jìn)程會(huì)占用資源并降低性能。幸運(yùn)的是,許多工具可以讓 Windows 保持平穩(wěn)運(yùn)行。
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)