如何基於 Redis 實現分佈式鎖

一、分佈式鎖的概念與特點

在分佈式系統中,爲了保證多個服務之間互斥地訪問共享資源,需要使用分佈式鎖。分佈式鎖的特點包括:

  1. 互斥性:同一時刻只能有一個線程持有鎖。

  2. 可重入性:同一節點上的同一個線程如果獲取了鎖之後能夠再次獲取鎖。

  3. 鎖超時:支持鎖超時,防止死鎖。

  4. 高性能和高可用:加鎖和解鎖需要高效,同時也需要保證高可用,防止分佈式鎖失效。

  5. 具備阻塞和非阻塞性:能夠及時從阻塞狀態中被喚醒。

二、Redis 分佈式鎖的實現原理

Redis 分佈式鎖的實現主要依賴於其提供的 SetNX 命令和 Lua 腳本的原子性操作。以下是 Redis 分佈式鎖的實現步驟:

  1. 獲取鎖:使用 SetNX 命令嘗試獲取鎖。SetNX 命令在指定的 key 不存在時,爲 key 設置指定的值。如果設置成功,則返回 1,表示獲取鎖成功;如果設置失敗,則返回 0,表示獲取鎖失敗。爲了避免死鎖,需要爲鎖設置一個超時時間,這樣即使持有鎖的客戶端崩潰,鎖也能在超時後被自動釋放。

  2. 設置超時時間:可以使用 Redis 的 Expire 命令爲鎖設置超時時間。但需要注意,SetNX 命令和 Expire 命令不是原子性操作,因此在高併發場景下可能會出現問題。爲了解決這個問題,可以使用 Lua 腳本將獲取鎖和設置超時時間兩個操作合併爲一個原子性操作。

  3. 釋放鎖:在釋放鎖時,需要確保只有持有鎖的客戶端才能釋放鎖。這可以通過在釋放鎖時比較鎖的 value 值來實現。如果 value 值與持有鎖的客戶端設置的 value 值相同,則刪除鎖;否則,不執行刪除操作。爲了確保釋放鎖的原子性,同樣需要使用 Lua 腳本。

三、Redis 分佈式鎖的案例分析

以下是一個基於 Redis 分佈式鎖的庫存扣減案例分析:

  1. 未使用鎖的情況:
  1. 使用 JVM 級別的鎖:
  1. 使用 Redis 分佈式鎖:

以下是一個簡單的 Redis 分佈式鎖實現示例(僞代碼):

String localKey = "lock:product:0001"; // 鎖的 key
String uniqueValue = UUID.randomUUID().toString(); // 唯一的 value 值作爲鎖的標識

// 嘗試獲取鎖
Boolean lockAcquired = redisClient.setIfAbsent(localKey, uniqueValue, timeout);
if (!lockAcquired) {
    // 獲取鎖失敗,返回提示信息
    return "當前系統繁忙";
}

try {
    // 獲取庫存值
    int stock = Integer.parseInt(redisClient.get("stock"));
    if (stock > 0) {
        int realStock = stock - 1;
        redisClient.set("stock", realStock + "");
        System.out.println("扣減成功,剩餘庫存:" + realStock);
    } else {
        System.out.println("扣減失敗,庫存不足");
    }
} finally {
    // 釋放鎖(使用 Lua 腳本保證原子性)
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    redisClient.eval(luaScript, Collections.singletonList(localKey), Collections.singletonList(uniqueValue));
}

在這個示例中,我們使用了 Redis 的 SetIfAbsent 命令來嘗試獲取鎖,並使用 Lua 腳本來釋放鎖。這樣可以確保獲取鎖和釋放鎖的操作都是原子性的,從而避免併發問題。

四、Redis 分佈式鎖的注意事項

  1. 鎖的粒度:鎖的粒度越細,併發性能越好,但可能會導致更多的鎖競爭和死鎖問題。因此,需要根據具體業務場景選擇合適的鎖粒度。

  2. 超時時間的設置:超時時間設置得太短,可能會導致鎖被提前釋放;超時時間設置得太長,可能會導致死鎖問題。因此,需要根據具體業務場景和系統的性能要求來設置合適的超時時間。

  3. 鎖的續期:如果業務執行時間比較長,而鎖的超時時間又比較短,可能會導致鎖在業務執行過程中被釋放。爲了解決這個問題,可以使用定時任務來續期鎖的超時時間。但需要注意,續期操作也需要是原子性的,以避免併發問題。

  4. 鎖的釋放:在釋放鎖時,需要確保只有持有鎖的客戶端才能釋放鎖。這可以通過比較鎖的 value 值來實現。如果 value 值不匹配,則不執行釋放操作。同時,釋放鎖的操作也需要是原子性的,以避免併發問題。

綜上所述,基於 Redis 實現分佈式鎖需要綜合考慮多個方面,包括鎖的獲取、超時時間的設置、鎖的續期和釋放等。通過合理的設計和實現,可以確保分佈式鎖的正確性和高效性,從而滿足分佈式系統的併發控制需求。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/T4fjnZAWl4ZaRR6YpEm9iw