【分散式緩存系列】集群環境下Redis分散式鎖的正確姿勢

来源:https://www.cnblogs.com/zhili/archive/2019/01/23/redLock_DistributedLock.html
-Advertisement-
Play Games

一、前言 在上一篇文章中,已經介紹了基於Redis實現分散式鎖的正確姿勢,但是上篇文章存在一定的缺陷——它加鎖只作用在一個Redis節點上,如果通過sentinel保證高可用,如果master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況: 於是,客戶端1和客戶端2同時持有了同一個資源的鎖 ...


一、前言

  在上一篇文章中,已經介紹了基於Redis實現分散式鎖的正確姿勢,但是上篇文章存在一定的缺陷——它加鎖只作用在一個Redis節點上,如果通過sentinel保證高可用,如果master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:

  1.  客戶端1在Redis的master節點上拿到了鎖
  2. Master宕機了,存儲鎖的key還沒有來得及同步到Slave上
  3. master故障,發生故障轉移,slave節點升級為master節點
  4. 客戶端2從新的Master獲取到了對應同一個資源的鎖

  於是,客戶端1和客戶端2同時持有了同一個資源的鎖。鎖的安全性被打破了。針對這個問題。Redis作者antirez提出了RedLock演算法來解決這個問題

二、RedLock演算法的實現思路

  antirez提出的redlock演算法實現思路大概是這樣的。

  客戶端按照下麵的步驟來獲取鎖:

  1. 獲取當前時間的毫秒數T1。
  2. 按順序依次向N個Redis節點執行獲取鎖的操作。這個獲取鎖的操作和上一篇中基於單Redis節點獲取鎖的過程相同。包括唯一UUID作為Value以及鎖的過期時間(expireTime)。為了保證在某個在某個Redis節點不可用的時候演算法能夠繼續運行,這個獲取鎖的操作還需要一個超時時間。它應該遠小於鎖的過期時間。客戶端向某個Redis節點獲取鎖失敗後,應立即嘗試下一個Redis節點。這裡失敗包括Redis節點不可用或者該Redis節點上的鎖已經被其他客戶端持有。
  3. 計算整個獲取鎖過程的總耗時。即當前時間減去第一步記錄的時間。計算公司為T2=now()- T1。如果客戶端從大多數Redis節點(>N/2 +1)成功獲取到鎖。並且獲取鎖總共消耗的時間小於鎖的過期時間(即T2<expireTime)。則認為客戶端獲取鎖成功,否則,認為獲取鎖失敗
  4. 如果獲取鎖成功,需要重新計算鎖的過期時間。它等於最初鎖的有效時間減去第三步計算出來獲取鎖消耗的時間,即expireTime - T2
  5. 如果最終獲取鎖失敗,那麼客戶端立即向所有Redis系欸但發起釋放鎖的操作。(和上一篇釋放鎖的邏輯一樣)

  雖然說RedLock演算法可以解決單點Redis分散式鎖的安全性問題,但如果集群中有節點發生崩潰重啟,還是會鎖的安全性有影響的。具體出現問題的場景如下:

  假設一共有5個Redis節點:A, B, C, D, E。設想發生瞭如下的事件序列:

  1. 客戶端1成功鎖住了A, B, C,獲取鎖成功(但D和E沒有鎖住)
  2. 節點C崩潰重啟了,但客戶端1在C上加的鎖沒有持久化下來,丟失了
  3. 節點C重啟後,客戶端2鎖住了C, D, E,獲取鎖成功

  這樣,客戶端1和客戶端2同時獲得了鎖(針對同一資源)。針對這樣場景,解決方式也很簡單,也就是讓Redis崩潰後延遲重啟,並且這個延遲時間大於鎖的過期時間就好。這樣等節點重啟後,所有節點上的鎖都已經失效了。也不存在以上出現2個客戶端獲取同一個資源的情況了。 

  相比之下,RedLock安全性和穩定性都比前一篇文章中介紹的實現要好很多,但要說完全沒有問題不是。例如,如果客戶端獲取鎖成功後,如果訪問共用資源操作執行時間過長,導致鎖過期了,後續客戶端獲取鎖成功了,這樣在同一個時刻又出現了2個客戶端獲得了鎖的情況。所以針對分散式鎖的應用的時候需要多測試。伺服器台數越多,出現不可預期的情況也越多。如果客戶端獲取鎖之後,在上面第三步發生了GC得情況導致GC完成後,鎖失效了,這樣同時也使得同一時間有2個客戶端獲得了鎖。如果系統對共用資源有非常嚴格要求得情況下,還是建議需要做資料庫鎖得得方案來補充。如飛機票或火車票座位得情況。對於一些搶購獲取,針對偶爾出現超賣,後續可以人為溝通置換得方式採用分散式鎖得方式沒什麼問題。因為可以絕大部分保證分散式鎖的安全性。

三、分散式場景下基於Redis實現分散式鎖的正確姿勢

  目前redisson包已經有對redlock演算法封裝,接下來就具體看看使用redisson包來實現分散式鎖的正確姿勢。

  具體實現代碼如下代碼所示:

  

public interface DistributedLock {
    /**
     * 獲取鎖
     * @author zhi.li
     * @return 鎖標識
     */
    String acquire();

    /**
     * 釋放鎖
     * @author zhi.li
     * @param indentifier
     * @return
     */
    boolean release(String indentifier);
}

public class RedisDistributedRedLock implements DistributedLock {

    /**
     * redis 客戶端
     */
    private RedissonClient redissonClient;

    /**
     * 分散式鎖的鍵值
     */
    private String lockKey;

    private RLock redLock;

    /**
     * 鎖的有效時間 10s
     */
    int expireTime = 10 * 1000;

    /**
     * 獲取鎖的超時時間
     */
    int acquireTimeout  = 500;

    public RedisDistributedRedLock(RedissonClient redissonClient, String lockKey) {
        this.redissonClient = redissonClient;
        this.lockKey = lockKey;
    }

    @Override
    public String acquire() {
        redLock = redissonClient.getLock(lockKey);
        boolean isLock;
        try{
            isLock = redLock.tryLock(acquireTimeout, expireTime, TimeUnit.MILLISECONDS);
            if(isLock){
                System.out.println(Thread.currentThread().getName() + " " + lockKey + "獲得了鎖");
                return null;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public boolean release(String indentifier) {
        if(null != redLock){
            redLock.unlock();
            return true;
        }

        return false;
    }
}

  由於RedLock是針對主從和集群場景準備。上面代碼採用哨兵模式。所以要讓上面代碼運行起來,需要先本地搭建Redis哨兵模式。本人的環境是Windows,具體Windows 哨兵環境搭建參考文章:redis sentinel部署(Windows下實現)

  具體測試代碼如下所示:

  

public class RedisDistributedRedLockTest {
    static int n = 5;
    public static void secskill() {
        if(n <= 0) {
            System.out.println("搶購完成");
            return;
        }

        System.out.println(--n);
    }
    public static void main(String[] args) {

        Config config = new Config();
        //支持單機,主從,哨兵,集群等模式
        //此為哨兵模式
        config.useSentinelServers()
                .setMasterName("mymaster")
                .addSentinelAddress("127.0.0.1:26369","127.0.0.1:26379","127.0.0.1:26389")
                .setDatabase(0);
        Runnable runnable = () -> {
            RedisDistributedRedLock redisDistributedRedLock = null;
            RedissonClient redissonClient = null;
            try {
                redissonClient = Redisson.create(config);
                redisDistributedRedLock = new RedisDistributedRedLock(redissonClient, "stock_lock");
                redisDistributedRedLock.acquire();
                secskill();
                System.out.println(Thread.currentThread().getName() + "正在運行");
            } finally {
                if (redisDistributedRedLock != null) {
                    redisDistributedRedLock.release(null);
                }

                redissonClient.shutdown();
            }
        };

        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }

  具體的運行結果,如下圖所示:

四、總結

  到此,基於Redis實現分散式鎖的就告一段落了,由於分散式鎖的實現方式主要有:資料庫鎖的方式、基於Redis實現和基於Zookeeper實現。接下來的一篇文章將介紹基於Zookeeper分散式鎖的正確姿勢。

  本文所有代碼地址:https://github.com/learninghard-lizhi/common-util 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 其旨在打造一個集應用開發、大數據存儲、處理、分散式計算、自動化部署的無節點微服務集中開發與運行平臺,以響應業務的快速變更,滿足系統對大數據,大併發與開發效率的需求; 平臺設計以數據為核,以groovy腳本為基礎,通過提供api、非同步消息處理、調度等基礎構件來支持應用的快速開發; 核心是通過整合現有開 ...
  • 對象實例化過程: 1.看類是否已載入,未載入的話先初始化類。 2.在堆記憶體中分配空間。 3.初始化父類的屬性 4.初始化父類的構造方法 5.初始化子類的屬性 6.初始化子類的構造方法 實例: package com.xm.load; public class Animal { static Stri ...
  • 我相信對於很多愛好和習慣寫博客的人來說,如果自己的博客有很多人閱讀和評論的話,自己會非常開心,但是你發現自己用心寫的博客卻沒什麼人看,多多少少會覺得有些傷心吧?我們今天就來看一下為什麼你的博客沒人看呢? 一、頁面分析 首先進入博客園首頁,可以看到一頁有20篇博客簡介,然後有200頁,也就是說總共有2 ...
  • 使用static關鍵字修飾的變數和方法為靜態變數、靜態方法。 非靜態方法可以訪問靜態變數/方法和非靜態變數/方法,但靜態方法只能訪問靜態變數/方法。 可以看到在靜態方法中調用非靜態變數和非靜態方法時,Java會報錯。 所謂的靜態是指變數或方法可以不依賴對象而直接使用類名來調用,這也是static的意 ...
  • 第74節:第74節:Java中的Cookie和Session : 什麼是 ,有什麼用哦,怎麼用呢? 啟動伺服器後,會給每個應用程式創建一個 ,並且這個 對象只有一個。可以用於獲取全局參數,工程下的資源,和存取數據,共用數據。 例子,如何獲取全局參數,如何獲取工程下的資源,如何進行存取數據,用例子代碼 ...
  • 今天在寫條件語句時,一老出錯 自認為程式上是沒什麼問題的,所以將逗號去掉試試看 得出了正確的值,很意外,然後多試了幾次之後,逗號也可以輸入了 原來我誤把中文輸入法的逗號輸入到scanf中,而運行時又用英文輸入法 然後得出來的總結就是,scanf函數中必須用自己制定的格式,輸入,輸出。 ...
  • If I had only one hour to save the worlds,I would spend fifty five minutes defining the problem,and only five minutes finding the solution. 如果我只有1小時拯救 ...
  • 函數名的使用: 函數名可以作為值,賦值給變數 函數名可以作為參數傳參給函數. 函數名可以作為返回值 函數名可以作為元素存儲在容器里 閉包:在嵌套函數內,使用外層局部變數(非全局變數)就是一個閉包,閉包可以多層嵌套.閉包的優點: 避免局部變數不被外界修改 函數生命周期延長 節省開闢空間,銷毀空間的時間 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...