哦喲!才知道,原來大廠的Redis分佈式鎖都這麼設計 本地鎖 2 分佈式鎖

本地鎖

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

2 分佈式鎖

2.1 分佈式鎖的原理

廁所佔坑理論

可同時去一個地方“佔坑”:

  • 佔到,就執行邏輯
  • 否則等待,直到釋放鎖

可通過自旋方式自旋

“佔坑”可以去Redis、DB、任何所有服務都能訪問的地方。

2.2 分佈式鎖演進

一階段

// 佔分布式鎖,去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();
}

問題場景

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

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

階段二

// 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命令

階段三

// 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()
}

階段四

已經拿到了 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";

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章