首頁  >  文章  >  資料庫  >  Redis分散式鎖一定要避開的兩個坑是什麼

Redis分散式鎖一定要避開的兩個坑是什麼

WBOY
WBOY轉載
2023-05-29 22:52:041222瀏覽

1 第一個坑:錯誤釋放鎖定時機

1.1. 發現問題

分析以下程式碼有什麼問題:

// 分布式锁服务
public interface RedisLockService {
    // 获取锁
    public boolean getLock(String key);
    // 释放锁
    public boolean releaseLock(String key);
}

// 业务服务
public class BizService {

    @Resource
    private RedisLockService redisLockService;

    public void bizMethod(String bizId) {
        try {
            // 获取锁
            if(redisLockService.getLock(bizId)) {
                // 业务重复校验
                if(!bizValidate(bizId)) {
                    throw new BizException(ErrorBizCode.REPEATED);
                }
                // 执行业务
                return doBusiness();
            }
            // 获取锁失败
            throw new BizException(ErrorBizCode.GET_LOCK_ERROR);
        } finally {
            // 释放锁
            redisLockService.releaseLock(bizId);
        }
    }
}

上述程式碼看似沒問題,實則隱藏大問題。問題在於釋放鎖定時沒有校驗目前執行緒是否拿到鎖定:

  • 執行緒1和執行緒2同一時刻存取業務方法

  • 執行緒2獲取鎖定成功,進行業務處理

  • 線程1沒有取得到鎖,但是釋放鎖成功

  • 此時有線程3嘗試獲取鎖定成功,但是線程2業務沒有處理完,所以線程3不會導致業務重複異常

  • #最終導致線程2和線程3重複執行業務

#1.2 解決問題

解決方案是在確認取得鎖定成功後才允許釋放鎖定:

public class BizService {

    @Resource
    private RedisLockService redisLockService;

    public void bizMethod(String bizId) {
        boolean getLockSuccess = false;
        try {
            // 尝试获取锁
            getLockSuccess = redisLockService.getLock(bizId);
            // 获取锁成功
            if(getLockSuccess) {
                // 业务重复校验
                if(!bizValidate(bizId)) {
                    throw new BizException(ErrorBizCode.REPEATED);
                }
                // 执行业务
                return doBusiness();
            }
            // 获取锁失败
            throw new BizException(ErrorBizCode.GET_LOCK_ERROR);
        } finally {
            // 获取锁成功才允许释放锁
            if(getLockSuccess) {
                redisLockService.releaseLock(bizId);
            }
        }
    }
}

2 第二個坑:快取失效問題

第二個問題是Redis還存在記憶體清理機制,可能會導致分散式鎖定失效。

2.1 過期清理機制

(1) 定期刪除

Redis定時檢查哪些key已經過期,發現過期則刪除

#(2) 惰性刪除

如果key非常多,定期刪除會非常消耗資源,所以引入惰性刪除策略

如果Redis存取key時發現已經過期則直接刪除

2.2 記憶體回收機制

當記憶體不足時Redis會選擇一些元素進行刪除:

no-enviction

禁止驅逐數據,新寫入操作會報錯

volatile-lru

從已設定過期時間的資料集選擇最近最少使用的資料淘汰

volatile-ttl

#從已設定過期時間的資料集選擇將要過期的資料淘汰

volatile-random

從已設定過期時間的資料集選擇任意的資料淘汰

allkeys- lru

從資料集選擇最近最少使用的資料淘汰

allkeys-random

從資料集選擇任意的資料淘汰

#至少存在兩種場景導致分散式鎖定失效問題:

  • 場景一:Redis記憶體不足進行記憶體回收,使用allkeys-lruallkeys-random回收策略導致鎖定失效

  • 場景二:執行緒取得分散式鎖定成功,但處理業務時間過長,此時鎖定到期被定時清理,導致其它執行緒取得鎖定成功並重複執行業務

2.3 樂觀鎖定

通用方案是在資料庫層保護,例如庫存扣減業務在資料庫層用樂觀鎖。

udpate goods set stock = stock - #{acquire} 
where sku_id = #{skuId} and stock - #{acquire} >= 0

以上是Redis分散式鎖一定要避開的兩個坑是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除