定製直播軟體,分散式鎖的演進你瞭解多少?

云豹科技-苏凌霄發表於2024-08-03

定製直播軟體,分散式鎖的演進你瞭解多少?

分散式鎖的演進

基本原理
我們可以同時去一個地方“佔坑”,如果佔到,就執行邏輯。否則就必須等待,直到釋放鎖。“佔坑”可以去redis,可以去資料庫,可以去任何大家都能訪問的地方。等待可以自旋的方式。

階段一

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
        //階段一
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        //獲取到鎖,執行業務
        if (lock) {
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            //刪除鎖,如果在此之前報錯或當機會造成死鎖
            stringRedisTemplate.delete("lock");
            return categoriesDb;
        }else {
            //沒獲取到鎖,等待100ms重試
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }
 
public Map<String, List<Catalog2Vo>> getCategoryMap() {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("快取不命中,準備查詢資料庫。。。");
            Map<String, List<Catalog2Vo>> categoriesDb= getCategoriesDb();
            String toJSONString = JSON.toJSONString(categoriesDb);
            ops.set("catalogJson", toJSONString);
            return categoriesDb;
        }
        System.out.println("快取命中。。。。");
        Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
        return listMap;
    }

問題: setnx佔好了位,業務程式碼異常或者程式在頁面過程中當機。沒有執行刪除鎖邏輯,這就造成了死鎖

解決: 設定鎖的自動過期,即使沒有刪除,會自動刪除

階段二

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        if (lock) {
            //設定過期時間
            stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            stringRedisTemplate.delete("lock");
            return categoriesDb;
        }else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

問題: setnx設定好,正要去設定過期時間,當機。又死鎖了。

解決: 設定過期時間和佔位必須是原子的。redis支援使用setnx ex命令。

階段三

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    //加鎖的同時設定過期時間,二者是原子性操作
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",5, TimeUnit.SECONDS);
    if (lock) {
        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
        //模擬超長的業務執行時間
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stringRedisTemplate.delete("lock");
        return categoriesDb;
    }else {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getCatalogJsonDbWithRedisLock();
    }
}

問題: 刪除鎖直接刪除???如果由於業務時間很長,鎖自己過期了,我們直接刪除,有可能把別人正在持有的鎖刪除了。

解決: 佔鎖的時候,值指定為uuid,每個人匹配是自己的鎖才刪除。

階段四

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
      //為當前鎖設定唯一的uuid,只有當uuid相同時才會進行刪除鎖的操作
        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
        if (lock) {
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            String lockValue = ops.get("lock");
            if (lockValue.equals(uuid)) {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stringRedisTemplate.delete("lock");
            }
            return categoriesDb;
        }else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

問題: 如果正好判斷是當前值,正要刪除鎖的時候,鎖已經過期,別人已經設定到了新的值。那麼我們刪除的是別人的鎖

解決: 刪除鎖必須保證原子性。使用redis+Lua指令碼完成

階段五-最終形態

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
        if (lock) {
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            String lockValue = ops.get("lock");
            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);
            return categoriesDb;
        }else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

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

以上就是定製直播軟體,分散式鎖的演進你瞭解多少?, 更多內容歡迎關注之後的文章

相關文章