【zookeeper】zookeeper分散式鎖

小碼農(微信公眾號:碼農吧)發表於2020-10-05

一、前言

二、從鎖到分散式鎖

2.1 分散式技術四個分支

分散式相關技術:分散式部署導致分散式鎖、分散式事務、分散式登入(單點登入)、分散式儲存HDFS四個技術的出現
1、後端服務分散式部署後,程式內Java併發多執行緒鎖(阻塞鎖+CAS)不適用了,出現了分散式鎖(三種實現方式:直接在Mysql表上實現分散式鎖、引入一箇中介軟體zookeeper實現各服務之間分散式鎖、引入一箇中介軟體redis實現各服務之間分散式鎖)
2、mysql分散式部署後,事務不適用了,出現了分散式事務(七種實現方式:兩階段、三階段、XA、最大努力、ebay本地訊息表、TCC程式設計模式、半訊息/最終一致性)
3、後端服務分散式部署後,程式內登入模組,服務端session不適用了,出現了分散式登入,即單點登入
4、分散式儲存HDFS

2.2 從鎖到分散式鎖(為什麼redis需要分散式鎖)

單個Java程式內的鎖
問題:在Java併發多執行緒環境下,由於上下文的切換,資料可能出現不一致的情況或者資料被汙染。
期待:當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問,直到該執行緒讀取完,其他執行緒才可使用。
實現兩種方式:Java中實現鎖有兩種方式,阻塞鎖(synchronized lock)或 CAS硬體層面鎖。

程式內的多執行緒鎖
互斥:互斥的機制,保證同一時間只有一個執行緒可以操作共享資源 synchronized,Lock等。
臨界值:讓多執行緒序列話去訪問資源
事件通知:通過事件的通知去保證大家都有序訪問共享資源
訊號量:多個任務同時訪問,同時限制數量,比如發令槍CDL(即CountDownLatch),Semaphore等

問題:為什麼Redis是單執行緒,但是需要分散式鎖?
標準答案:Redis是單執行緒,所以單個redis內部不需要程式內鎖機制,這是可以確定的;
如果後端採用單體架構,一個Redis對應一個服務,不需要在redis中實現分散式鎖;
如果後端採用微服務架構,一個Redis對應多個SOA服務,需要在redis中實現分散式鎖。
解釋:如果後端採用微服務架構,不使用任何分散式鎖機制(mysql、zookeeper、redis)
電商網站中的秒殺,拿到庫存判斷,然後在減庫存,雙11之前,為了減少DB的壓力,擋住第一批最大秒殺,把庫存預熱到了KV,現在KV的庫存是1。服務A去Redis查詢到庫存發現是1,那說明我能搶到這個商品對不對,那我就準備減一了,但是還沒減。同時服務B也去拿發現也是1,那我也搶到了呀,那我也減。C同理。等所有的服務都判斷完了,你發現誒,怎麼變成-2了,超賣了呀,這下完了。

2.3 zookeeper五個用途 + zookeeper儲存 + zookeeper四種節點建立

zookeeper開發中的使用場景/zookeeper有什麼用
開發用途1:服務註冊與訂閱(共用節點,類似於eureka,作為註冊中心)
開發用途2:分散式通知(使用 監聽znode 實現)
開發用途3:服務命名(使用 znode特性 實現)
開發用途4:資料訂閱、釋出(使用 watcher 實現)
開發用途5:分散式鎖(使用 臨時順序節點 實現,三種分散式鎖:mysql zookeeper )

金手指:zookeeper儲存
問題1:zookeeper是什麼?
回答1:zookeeper是個資料庫,檔案儲存系統,並且有監聽通知機制(觀察者模式)
問題2:接上面,zookeeper作為一個類Unix檔案系統,它是如何儲存資料的?
回答2:zookeeper通過節點來儲存資料。
問題3:接上面,zookeeper節點?
回答3:zk的節點型別有4大類:持久化節點(zk斷開節點還在)、持久化順序編號目錄節點、臨時目錄節點(客戶端斷開後節點就刪除了)、臨時目錄編號目錄節點。
注意:節點名稱都是唯一的,這是zookeeper實現分散式鎖互斥的基石。

使用層面:zookeeper節點怎麼建立(linux操作)?
create /test laogong // 建立永久節點
create -e /test laogong // 建立臨時節點
create -s /test // 建立順序節點
create -e -s /test // 建立臨時順序節點
臨時節點與永久節點:退出後,重新連線,上一次建立的所有臨時節點都沒了,但是永久節點還有。

三、程式內無鎖–程式內加鎖–zookeeper分散式鎖–zookeeper分散式鎖

3.1 程式內不是鎖,造成執行緒安全問題

public class Test implements Runnable {
    static int inventory = 1;   //  要在main函式中用,設定為static
    private static final int NUM = 10; //  要在main函式中用,設定為static
    private static CountDownLatch countDownLatch = new CountDownLatch(NUM); //  要在main函式中用,設定為static

    public static void main(String[] args) {
        Test test = new Test();   // 10個執行緒都是用同一個test物件
        for (int i = 1; i <= NUM; i++) {
            new Thread(test).start();   // 新建執行緒並啟動
            countDownLatch.countDown();  // 減1
        }
    }

    @Override
    public void run() {   // 執行緒執行的具體邏輯在run()裡面
        try {
            countDownLatch.await();  // 一定到countDownLatch==0才可以通過阻塞  第一,為什麼要使用發令槍?不用發令槍也可以,使用發令槍只是讓10個執行緒啟動更加同時,不會先建立的執行緒先啟動
            if (inventory > 0) {  // 就是inventory ==1 可以進入
                Thread.sleep(1);  // 第二,為什麼加上Thread.sleep()if (inventory > 0)和 inventory --;中間加上Thread.sleep();  讓執行緒安全問題暴露的更明顯
                inventory--;  // 在inventory--,前面休息5ms   讓執行緒安全問題暴露的更明顯
            }
            System.out.println(inventory);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
執行結果:
-1
-1
-2
-3
-3
-3
-3
-3
-3
-3

解釋程式碼:
第一,為什麼要使用發令槍?不用發令槍也可以,使用發令槍只是讓10個執行緒啟動更加同時,不會先建立的執行緒先啟動
第二,為什麼加上Thread.sleep()? 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯

3.2 程式內使用鎖(synchronized / lock)

public class Test1 implements Runnable {
    private final Lock _lock = new ReentrantLock();
    static int inventory = 1;   //  要在main函式中用,設定為static
    private static final int NUM = 10; //  要在main函式中用,設定為static
    private static CountDownLatch countDownLatch = new CountDownLatch(NUM); //  要在main函式中用,設定為static


    public static void main(String[] args) {
        Test1 test1=new Test1();
        for (int i = 1; i <= NUM; i++) {
            new Thread(test1).start();   // 新建執行緒並啟動   第三,這個10個執行緒,一定要傳入同一個物件test1,否則鎖不起作用
          countDownLatch.countDown();  // 減1
        }
    }

    @Override
    public void run() {   // 執行緒執行的具體邏輯在run()裡面

        _lock.lock();
        try {
               countDownLatch.await();  // 一定到countDownLatch==0才可以通過阻塞
            if (inventory > 0) {  // 就是inventory ==1 可以進入
                Thread.sleep(5);  // 休息5ms   讓執行緒安全問題暴露的更明顯
                inventory--;  // 在inventory--,前面休息5ms   讓執行緒安全問題暴露的更明顯
            }
            System.out.println(inventory);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            _lock.unlock();
        }

//    try {
//        countDownLatch.await();  // 一定到countDownLatch==0才可以通過阻塞
//        synchronized (this){
//            if (inventory > 0) {  // 就是inventory ==1 可以進入
//                Thread.sleep(5);  // 休息5ms   讓執行緒安全問題暴露的更明顯
//                inventory--;  // 在inventory--,前面休息5ms   讓執行緒安全問題暴露的更明顯
//            }
//            System.out.println(inventory);
//        }
//
//    } catch (Exception e) {
//        e.printStackTrace();
//    }


    }
}

解釋程式碼:
第一,為什麼要使用發令槍?不用發令槍也可以,使用發令槍只是讓10個執行緒啟動更加同時,不會先建立的執行緒先啟動
第二,為什麼加上Thread.sleep()? 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯
第三,這個10個執行緒,一定要傳入同一個物件test1,否則鎖不起作用

3.3 zookeeper分散式鎖(加鎖+解鎖+監聽)

public class Test2 implements Runnable {

    private static final String IP_PORT = "127.0.0.1:2181";
    private static final String Z_NODE = "/LOCK";
    private static final int NUM = 10; //  要在main函式中用,設定為static
    private static CountDownLatch countDownLatch = new CountDownLatch(NUM); //  要在main函式中用,設定為static
    private static ZkClient zkClient = new ZkClient(IP_PORT);
    static int inventory = 1;   //  要在main函式中用,設定為static
    private static final int NUM = 10; //  要在main函式中用,設定為static

    public static void main(String[] args) {
        Test2 test2 = new Test2();   // 10個執行緒都是用同一個test物件
        for (int i = 1; i <= NUM; i++) {
            new Thread(test2).start();   // 新建執行緒並啟動  分散式鎖3:原來是一個程式中10個執行緒,用發令槍控制10個執行緒同時跑
            // 現在,一個後端節點一個執行緒,所以10個後端節點10個執行緒,不需要發令槍了,它只能控制同一程式裡面的執行緒一起跑
        }
    }

    @Override
    public void run() {   // 執行緒執行的具體邏輯在run()裡面
        try {
            new Test2().lock();
            zkClient.createPersisent(Z_NODE);   // 分散式鎖1:新建一個節點   節點是唯一的,已經有了,再次新建就會報錯,用新建節點這個特性來實現分散式鎖,相當於synchronized | lock
            if (inventory > 0) {  // 就是inventory ==1 可以進入
                Thread.sleep(1);  // 第二,為什麼加上Thread.sleep()if (inventory > 0)和 inventory --;中間加上Thread.sleep();  讓執行緒安全問題暴露的更明顯
                inventory--;  // 在inventory--,前面休息5ms   讓執行緒安全問題暴露的更明顯
            }
            System.out.println(inventory);
            return;
        } finally {   // 分散式鎖4:去掉catch塊
            new Test2().unlock();
            // 分散式鎖2:在finally塊,釋放節點,類似lock.unlock    即,最開始新建節點類似lock.lock() finally塊釋放節點類似 lock.unlock();
            System.out.println("釋放鎖");
        }
    }

    public void lock() {
        if (tryLock()) {  // 加鎖成功,直接return
            return;
        }
        waitForLock();  // 加鎖失敗,然後等待,突破發令槍阻塞再開始
        lock();  // 突破發令槍阻塞,再次嘗試加鎖
    }

    public void waitForLock() {    // 監聽  
        System.out.println("加鎖失敗");
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String arg0, Object arg1) throws Exception {  // 修改

            }

            @Override
            public void handleDataDeleted(String arg0) throws Exception {
                System.out.println("監聽到配置檔案被刪除,喚醒");  // config刪除,監聽列印
            }
        };
        zkClient.subscribeDataChanges(Z_NODE, listener);
        if (zkClient.exists(Z_NODE)) {
            try {
                countDownLatch.await();  // 發令槍阻塞,從這裡開始
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        zkClient.unsubscribeDataChanges(Z_NODE, listener);
    }

}

3.4 zookeeper分散式鎖(加鎖+解鎖+監聽+解決羊群效應)

public class Test3 implements Runnable {

    private static final String IP_PORT = "127.0.0.1:2181";
    private static final String Z_NODE = "/LOCK";
    private static final int NUM = 10; //  要在main函式中用,設定為static
    private static CountDownLatch countDownLatch = new CountDownLatch(NUM); //  要在main函式中用,設定為static
    private static ZkClient zkClient = new ZkClient(IP_PORT);
    static int inventory = 1;   //  要在main函式中用,設定為static
    private static final int NUM = 10; //  要在main函式中用,設定為static

    public static void main(String[] args) {
        Test3 test3 = new Test3();   // 10個執行緒都是用同一個test物件
        for (int i = 1; i <= NUM; i++) {
            new Thread(test3).start();   // 新建執行緒並啟動  分散式鎖3:原來是一個程式中10個執行緒,用發令槍控制10個執行緒同時跑
            // 現在,一個後端節點一個執行緒,所以10個後端節點10個執行緒,不需要發令槍了,它只能控制同一程式裡面的執行緒一起跑
        }
    }

    @Override
    public void run() {   // 執行緒執行的具體邏輯在run()裡面
        try {
            new Test2().lock();
            zkClient.createPersisent(Z_NODE);   // 分散式鎖1:新建一個節點   節點是唯一的,已經有了,再次新建就會報錯,用新建節點這個特性來實現分散式鎖,相當於synchronized | lock
            if (inventory > 0) {  // 就是inventory ==1 可以進入
                Thread.sleep(1);  // 第二,為什麼加上Thread.sleep()if (inventory > 0)和 inventory --;中間加上Thread.sleep();  讓執行緒安全問題暴露的更明顯
                inventory--;  // 在inventory--,前面休息5ms   讓執行緒安全問題暴露的更明顯
            }
            System.out.println(inventory);
            return;
        } finally {   // 分散式鎖4:去掉catch塊
            new Test2().unlock();
            // 分散式鎖2:在finally塊,釋放節點,類似lock.unlock    即,最開始新建節點類似lock.lock() finally塊釋放節點類似 lock.unlock();
            System.out.println("釋放鎖");
        }
    }

    public void lock() {
        if (tryLock()) {  // 加鎖成功,直接return
            System.out.println("獲得鎖");
        } else {
            waitForLock();  // 加鎖失敗,然後等待,突破發令槍阻塞再開始
            lock();  // 突破發令槍阻塞,再次嘗試加鎖
        }

    }


    public synchronized boolean tryLock() {
        if (StringUtils.isBlank(path))
            path = zkClient.createEphemeralSequential(Z_NODE + "/", "lock");

        List<String> children = zkClient.getClildren(Z_NODE);
        Collections.sort(children);

        if (path.equals(Z_NODE + "/" + children.get(0))){
            System.out.println("I am true");
            return true;
        }else {
            int i=Collections.binarySearch(children,path.substring(Z_NODE.length() + 1));
            beforePath= Z_NODE +"/"+children.get(i-1);
        }
        return false;
    }

    public void waitForLock() {
        System.out.println("加鎖失敗");
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String arg0, Object arg1) throws Exception {  // 修改

            }

            @Override
            public void handleDataDeleted(String arg0) throws Exception {
                System.out.println("監聽到配置檔案被刪除,喚醒");  // config刪除,監聽列印
            }
        };
        zkClient.subscribeDataChanges(beforePath, listener);
        if (zkClient.exists(Z_NODE)) {
            try {
                countDownLatch.await();  // 發令槍阻塞,從這裡開始
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        zkClient.unsubscribeDataChanges(beforePath, listener);
    }

}

四、面試金手指

4.1 分散式 + 從鎖到分散式鎖 + zookeeper五個用途 + zookeeper儲存 + zookeeper四種節點建立

4.1.1 分散式技術四個分支

分散式相關技術:分散式部署導致分散式鎖、分散式事務、分散式登入(單點登入)、分散式儲存HDFS四個技術的出現
1、後端服務分散式部署後,程式內Java併發多執行緒鎖(阻塞鎖+CAS)不適用了,出現了分散式鎖(三種實現方式:直接在Mysql表上實現分散式鎖、引入一箇中介軟體zookeeper實現各服務之間分散式鎖、引入一箇中介軟體redis實現各服務之間分散式鎖)
2、mysql分散式部署後,事務不適用了,出現了分散式事務(七種實現方式:兩階段、三階段、XA、最大努力、ebay本地訊息表、TCC程式設計模式、半訊息/最終一致性)
3、後端服務分散式部署後,程式內登入模組,服務端session不適用了,出現了分散式登入,即單點登入
4、分散式儲存HDFS

4.1.2 從鎖到分散式鎖(為什麼redis需要分散式鎖)

單個Java程式內的鎖
問題:在Java併發多執行緒環境下,由於上下文的切換,資料可能出現不一致的情況或者資料被汙染。
期待:當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問,直到該執行緒讀取完,其他執行緒才可使用。
實現兩種方式:Java中實現鎖有兩種方式,阻塞鎖(synchronized lock)或 CAS硬體層面鎖。

程式內的多執行緒鎖
互斥:互斥的機制,保證同一時間只有一個執行緒可以操作共享資源 synchronized,Lock等。
臨界值:讓多執行緒序列話去訪問資源
事件通知:通過事件的通知去保證大家都有序訪問共享資源
訊號量:多個任務同時訪問,同時限制數量,比如發令槍CDL(即CountDownLatch),Semaphore等

問題:為什麼Redis是單執行緒,但是需要分散式鎖?
標準答案:Redis是單執行緒,所以單個redis內部不需要程式內鎖機制,這是可以確定的;
如果後端採用單體架構,一個Redis對應一個服務,不需要在redis中實現分散式鎖;
如果後端採用微服務架構,一個Redis對應多個SOA服務,需要在redis中實現分散式鎖。
解釋:如果後端採用微服務架構,不使用任何分散式鎖機制(mysql、zookeeper、redis)
電商網站中的秒殺,拿到庫存判斷,然後在減庫存,雙11之前,為了減少DB的壓力,擋住第一批最大秒殺,把庫存預熱到了KV,現在KV的庫存是1。服務A去Redis查詢到庫存發現是1,那說明我能搶到這個商品對不對,那我就準備減一了,但是還沒減。同時服務B也去拿發現也是1,那我也搶到了呀,那我也減。C同理。等所有的服務都判斷完了,你發現誒,怎麼變成-2了,超賣了呀,這下完了。

4.1.3 zookeeper五個用途 + zookeeper儲存 + zookeeper四種節點建立

zookeeper開發中的使用場景/zookeeper有什麼用
開發用途1:服務註冊與訂閱(共用節點,類似於eureka,作為註冊中心)
開發用途2:分散式通知(使用 監聽znode 實現)
開發用途3:服務命名(使用 znode特性 實現)
開發用途4:資料訂閱、釋出(使用 watcher 實現)
開發用途5:分散式鎖(使用 臨時順序節點 實現,三種分散式鎖:mysql zookeeper )

金手指:zookeeper儲存
問題1:zookeeper是什麼?
回答1:zookeeper是個資料庫,檔案儲存系統,並且有監聽通知機制(觀察者模式)
問題2:接上面,zookeeper作為一個類Unix檔案系統,它是如何儲存資料的?
回答2:zookeeper通過節點來儲存資料。
問題3:接上面,zookeeper節點?
回答3:zk的節點型別有4大類:持久化節點(zk斷開節點還在)、持久化順序編號目錄節點、臨時目錄節點(客戶端斷開後節點就刪除了)、臨時目錄編號目錄節點。
注意:節點名稱都是唯一的,這是zookeeper實現分散式鎖互斥的基石。

使用層面:zookeeper節點怎麼建立(linux操作)?
create /test laogong // 建立永久節點
create -e /test laogong // 建立臨時節點
create -s /test // 建立順序節點
create -e -s /test // 建立臨時順序節點
臨時節點與永久節點:退出後,重新連線,上一次建立的所有臨時節點都沒了,但是永久節點還有。

4.2 zookeeper是如何基於節點去實現各種分散式鎖的?

總問題:zookeeper是如何基於節點去實現各種分散式鎖的?
問題1:zookeeper如何實現分散式鎖?
回答1:和lock一樣的,在子節點邏輯之前建立節點,在finally塊中釋放節點,兩行程式碼實現分散式鎖
解釋1:
(1)在zookeeper中,節點名為唯一的,給定節點名建立一個後就不能再建立一個,會報錯,建立指定節點名的節點,類似於獲得到了鎖;
(2)與lock不同的是,lock是一個程式內,對所有執行緒可見,而節點名是對 分散式部署 中所有的節點可見;
(3)正常情況下,會執行finally的語句,即表示建立節點,執行完邏輯後,正常釋放節點。

問題1.1 :zookeeper如何實現分散式鎖的互斥?
回答1.1 :zk節點有個唯一的特性,就是我們建立過這個節點了,你再建立zk是會報錯的,那我們就利用一下他的唯一性去實現一下。我們全部去建立,建立成功的第一個返回true他就可以繼續下面的扣減庫存操作,後續的節點訪問就會全部報錯,扣減失敗,我們把它們丟一個佇列去排隊。
問題1.2 :zookeeper實現的分散式鎖,客戶端如何釋放鎖?
回答1.2 :刪除節點,Lock在finally裡面unLock,現在我們在finally刪除節點,刪了再通知其他的客戶端來爭奪鎖。
問題1.3 :怎麼通知其他的客戶端來爭奪鎖?
回答1.3 :監聽節點的刪除事件,知道指定節點名的節點被刪除了,然後監聽函式中就開始爭奪 建立指定節點名節點。

問題2:接上面,上面說的是成功建立指定節點名的節點,在正常執行完邏輯後,正常執行完邏輯後,在finally中釋放節點,但是,如果佔有鎖的節點當機之後,造成的死鎖的問題,如何處理?
回答2:臨時節點,zookeeper有四種節點,永久節點在java客戶端程式斷開連線後會保留,臨時節點在java客戶端程式斷開連線後會銷燬,建立臨時節點,這樣,一旦java後端建立節點的SOA服務,這個客戶端獲取到鎖之後突然掛掉(Session連線斷開),這個臨時節點會被銷燬,其他客戶端自動獲取鎖。
問題3:客戶端的監聽機制(監聽指定節點名的節點的 操作和刪除)是所有服務都去監聽一個節點的,節點的釋放也會通知所有的伺服器,如果是900個伺服器呢?這對伺服器是很大的一個挑戰,一個釋放的訊息,就好像一個牧羊犬進入了羊群,大家都四散而開,隨時可能幹掉機器,會佔用服務資源,網路頻寬等等。造成羊群效應,如何處理羊群效應?
回答3:臨時順序節點,可以順利解決這個問題。其他所有節點都去監聽一個節點問題很大,但是監聽自己的前一個節點,因為是順序的,很容易找到自己的前後。因為每個節點只監聽了自己的前一個節點,釋放當然也是一個個釋放下去,就不會出現羊群效應了。
問題4:加鎖建立節點,但是你得實現一個阻塞的效果呀,那咋搞?
回答4:死迴圈,遞迴不斷去嘗試,直到成功,一個偽裝的阻塞效果。zk通過節點排隊監聽的機制,實現了阻塞的原理,其實就是個遞迴在那無限等待最小節點釋放的過程。
問題5:zookeeper使用指定節點名的唯一性,如何實現分散式鎖的可重入?
回答5:基於zookeeper的分散式鎖實現可重入鎖,可以帶上執行緒資訊,或者機器資訊這樣的唯一標識(只要是可以唯一表時間客戶端連線的就好),獲取的時候判斷一下,如果是以獲取鎖的客戶端,就不用再獲取了,如果不是以獲取鎖的客戶端,還需要重新獲取。
問題6:zookeeper叢集實現的分散式鎖,是否高可用?
回答6:zookeeper是一個分散式叢集架構的,是高可用的,對於寫操作,follower ACK只要半數以上,就成功了。
問題7:用zookeeper來實現分散式鎖的缺點?
回答7:
缺點1:zookeeper效能上可能並沒有快取服務redis那麼高(建立和刪除節點,比較重)。

解釋:因為每次在建立鎖和釋放鎖的過程中,都要動態建立、銷燬瞬時節點來實現鎖功能。這是非常重量的,ZK中建立和刪除節點只能通過Leader伺服器來執行,然後將資料同步到所有的Follower機器上。
缺點2:使用Zookeeper實現分散式鎖,可能帶來併發問題,只是並不常見而已。
解釋:由於網路抖動,客戶端可zookeeper叢集的session連線斷了,那麼zk以為客戶端掛了,就會刪除臨時節點,這時候其他客戶端就可以獲取到分散式鎖了。就可能產生併發問題了,這個問題不常見是因為zk有重試機制,一旦zk叢集檢測不到客戶端的心跳,就會重試,Curator客戶端支援多種重試策略。多次重試之後還不行的話才會刪除臨時節點。所以,選擇一個合適的重試策略也比較重要,要在鎖的粒度和併發之間找一個平衡。

五、小結

zookeeper分散式鎖,完成了。

天天打碼,天天進步!!!

相關文章