RedLock: 看完這篇文章後請不要有任何疑惑了

公衆號後臺經常會有小夥伴諮詢 RedLock 相關問題,筆者在此再來一篇文章剖析一下 RedLock,希望此文能解決你對它所有的疑惑和誤解。

爲什麼需要 RedLock

這一點很好理解,因爲普通的分佈式鎖算法在加鎖時它的 KEY 只會存在於某一個 Redis Master 實例以及它的 slave 上(假如有 slave 的話, 即使 cluster 集羣模式,也是一樣的。因爲一個 KEY 只會屬於一個 slot,一個 slot 只會屬於一個 Redis 節點),如下圖所示(圖中虛線表示 cluster 中 gossip 協議交互路徑):

因爲它只會存在於某一個 Redis Master 上,而 Redis 又不是基於 CP 模型的。那麼就會有很大概率存在鎖丟失的情況。以如下場景爲例:

1、線程 T1 在 M1 中加鎖成功。

2、M1 出現故障,但是由於主從同步延遲問題,加鎖的 KEY 並沒有同步到 S1 上。

3、S1 升級爲 Master 節點。

4、另一個線程 T2 在 S1 上也加鎖成功,從而導致線程 T1 和 T2 都獲取到了分佈式鎖。

而 RedLock 方法就是根除普通基於 Redis 分佈式鎖而生的(無論是主從模式、sentinel 模式還是 cluster 模式)!官方把 RedLock 方法當作使用 Redis 實現分佈式鎖的規範算法,並認爲這種實現比普通的單實例或者基於 Redis Cluster 的實現更安全。

RedLock 定義

首先,我們要掌握 RedLock 的第一步就是了解它的定義。這一點,官方網站肯定是最權威的。接下來的這段文字摘自 http://redis.cn/topics/distlock.html:

在 Redis 的分佈式環境中,我們假設有 N 個 Redis Master。這些節點完全互相獨立,不存在主從複製或者其他集羣協調機制(這句話非常重要,如果沒有理解這句話,也就無法理解 RedLock。並且由這句話我們可以得出,RedLock 依賴的環境不能是一個由 N 主 N 從組成的 Cluster 集羣模式,因爲 Cluster 模式下的各個 Master 並不完全獨立,而是存在 Gossip 協調機制的)。

接下來,我們假設有 3 個完全相互獨立的 Redis Master 單機節點,所以我們需要在 3 臺機器上面運行這些實例,如下圖所示(請注意這張圖中 3 個 Master 節點完全相互獨立):

爲了取到鎖,客戶端應該執行以下操作:

  1. 獲取當前 Unix 時間,以毫秒爲單位。

  2. 依次嘗試從 N 個 Master 實例使用相同的 key 和隨機值獲取鎖(假設這個 key 是 LOCK_KEY)。當向 Redis 設置鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間爲 10 秒,則超時時間應該在 5-50 毫秒之間。這樣可以避免服務器端 Redis 已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該儘快嘗試另外一個 Redis 實例。

  3. 客戶端使用當前時間減去開始獲取鎖時間(步驟 1 記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數的 Redis 節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖纔算獲取成功。

  4. 如果取到了鎖,key 的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟 3 計算的結果)。

  5. 如果因爲某些原因,獲取鎖失敗(沒有在至少 N/2+1 個 Redis 實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的 Redis 實例上進行解鎖(即便某些 Redis 實例根本就沒有加鎖成功)。

基於 Redisson 實現 RedLock

RedLock 方案並不是很複雜,但是如果我們自己去實現一個工業級的 RedLock 方案還是有很多坑的。幸運的是,Redisson 已經爲我們封裝好了 RedLock 的開源實現,假設基於 3 個單機 Redis 實例實現 RedLock 分佈式鎖,即第二張圖所示的 RedLock 方案,其源碼如下所示,非常簡單:

Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.0.1:6379")
        .setPassword("afeiblog").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.0.2:6379")
        .setPassword("afeiblog").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.0.3:6379")
        .setPassword("afeiblog").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);

String resourceName = "LOCK_KEY";

RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
// 向3個redis實例嘗試加鎖
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;
try {
    // isLock = redLock.tryLock();
    // 500ms拿不到鎖, 就認爲獲取鎖失敗。10000ms即10s是鎖失效時間。
    isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
    System.out.println("isLock = "+isLock);
    if (isLock) {
        //TODO if get lock success, do something;
    }
} catch (Exception e) {
} finally {
    // 無論如何, 最後都要解鎖
    redLock.unlock();
}

這段源碼有幾個要點:

  1. 首先需要構造 N 個 RLock(源碼中是 3 個,RLock 就是普通的分佈式鎖)。

  2. 然後用這 N 個 RLock 構造一個 RedissonRedLock,這就是 Redisson 給我們封裝好的 RedLock 分佈式鎖(即 N 個相互完全獨立的節點)。

  3. 調用 unlock 方法解鎖,這個方法會向每一個 RLock 發起解鎖請求(for (RLock lock : locks) {futures.add(lock.unlockAsync());})。

這段源碼我們是基於 3 個完全獨立的 Redis 單機實例來實現的(config1.useSingleServer())。當然,我們也可以基於 3 個完全獨立的主從(config.useMasterSlaveServers()),或者 3 個完全獨立的 sentinel 集羣(config.useSentinelServers()),或者 3 個完全獨立的 Cluster 集羣(config.useClusterServers().)。

假如我們依賴 3 個完全獨立的 Cluster 集羣來實現 ReLock 方案,那麼架構圖如下所示:

有些同學會反問,爲什麼需要 3 個 Redis Cluster,一個行不行?回答這個問題之前,我們假設只有一個 Redis Cluster,那麼無論這個 Cluster 有多少個 Master,我們是沒辦法讓 LOCK_KEY 發送到多個 Master 上的,因爲一個 KEY 只會屬於 Cluster 中的一個 Master,這一點也是沒理解 RedLock 方案最容易犯的錯誤。

最後還有一個小小的注意點,Redis 分佈式鎖加鎖時 value 必須唯一,RedLock 是怎麼保證的呢?答案是 UUID + threadId,源碼如下:

protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
    return id + ":" + threadId;
}

寫在最後

RedLock 方案相比普通的 Redis 分佈式鎖方案可靠性確實大大提升。但是,任何事情都具有兩面性,因爲我們的業務一般只需要一個 Redis Cluster,或者一個 Sentinel,但是這兩者都不能承載 RedLock 的落地。如果你想要使用 RedLock 方案,還需要專門搭建一套環境。所以,如果不是對分佈式鎖可靠性有極高的要求(比如金融場景),不太建議使用 RedLock 方案。當然,作爲基於 Redis 最牛的分佈式鎖方案,你依然必須掌握的非常好,以便在有需要時(比如面試)能應付自如。

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