大廠的Redis鎖居然是這麼用的,難怪不會超賣!

1 本地鎖

經常使用的即 synchronize 或 Lock 等 JDK 自帶的鎖,只能鎖住當前進程,僅適用於單體架構服務。 而在分佈式多服務實例場景下必須使用分佈式鎖javascript

大廠的Redis鎖居然是這麼用的,難怪不會超賣!

2 分佈式鎖

2.1 分佈式鎖的原理java

廁所佔坑理論redis

可同時去一個地方「佔坑」:架構

  • 佔到,就執行邏輯
  • 不然等待,直到釋放鎖

可經過自旋方式自旋分佈式

「佔坑」能夠去Redis、DB、任何全部服務都能訪問的地方。ide

大廠的Redis鎖居然是這麼用的,難怪不會超賣!

2.2 分佈式鎖演進學習

一階段ui

// 佔分布式鎖,去redis佔坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
if(lock) {
    //加鎖成功... 執行業務
    Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
    redisTemplate . delete( key: "lock");//fHßti
    return dataF romDb ;
} else {
    // 加鎖失敗,重試。synchronized()
    // 休眠100ms重試
    // 自旋
    return getCatalogJsonFromDbwithRedisLock();
}
關注公衆號:麒麟改bug,可獲取Redis實戰學習筆記一份。

大廠的Redis鎖居然是這麼用的,難怪不會超賣!

問題場景3d

  • setnx佔好了坑,可是業務代碼異常或程序在執行過程當中宕機,即沒有執行成功刪除鎖邏輯,致使死鎖

解決方案:設置鎖的自動過時,即便沒有刪除,會自動刪除。code

階段二

大廠的Redis鎖居然是這麼用的,難怪不會超賣!

// 1. 佔分布式鎖,去redis佔坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent( "lock", "110")
if(lock) {
    // 加鎖成功...執行業務

    // 忽然斷電

    // 2. 設置過時時間
    redisTemplate.expire("lock", timeout: 30, TimeUnit.SECONDS) ;
    Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
    //刪除鎖
    redisTemplate. delete( key; "lock");
    return dataFromDb;
} else {
    // 加鎖失敗...重試。 synchronized ()
    // 休眠100ms重試
    // 自旋的方式
    return getCatalogJsonF romDbWithRedisLock();
}

問題場景

  • setnx設置好,正要去設置過時時間,宕機,又死鎖

解決方案:設置過時時間和佔位必須是原子操做。redis支持使用setNxEx命令

階段三

大廠的Redis鎖居然是這麼用的,難怪不會超賣!

// 1. 分佈式鎖佔坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "110", 300, TimeUnit.SECONDS);
if(lock)(
    // 加鎖成功,執行業務

    // 2. 設置過時時間,必須和加鎖一塊兒做爲原子性操做
    // redisTemplate. expire( "lock", з0, TimeUnit.SECONDS);
    Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
    // 刪除鎖
    redisTemplate.delete( key: "lock")
    return dataFromDb;
else {
    // 加鎖失敗,重試
    // 休眠100ms重試
    // 自旋
    return getCatalogJsonFromDbithRedislock()
}

階段四

大廠的Redis鎖居然是這麼用的,難怪不會超賣!

已經拿到了 lockvalue ,有了 UUID,可是過時瞭如今!其餘人拿到所鎖設置了新值,因而 if 後將別人的鎖刪了!!也就是刪除鎖不是原子操做。

Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
String lockValue = redisTemplate.opsForValue().get("lock");
if(uuid.equals(lockValue)) {
    // 刪除我本身的鎖
    redisTemplate.delete("lock");
}

問題場景

  • 若是正好判斷是當前值,正要刪除鎖時,鎖已過時,別人已設置成功新值。那刪除的就是別人的鎖.
  • 解決方案

刪除鎖必須保證原子性。使用redis+Lua腳本。

階段五

  • 確保加鎖/解鎖都是原子操做
String script = 
    "if redis.call('get', KEYS[1]) == ARGV[1] 
        then return redis.call('del', KEYS[1]) 
    else 
        return 0 
    end";

    關注公衆號:麒麟改bug,可獲取Redis實戰學習筆記一份。

保證加鎖【佔位+過時時間】和刪除鎖【判斷+刪除】的原子性。 更難的事情,鎖的自動續期。

相關文章
相關標籤/搜索