ZK分布式鎖(未完 待續)


實現思路

公平鎖:創建有序節點,判斷本節點是不是序號最小的節點(第一個節點),若是,則獲取鎖;若不是,則監聽比該節點小的那個節點的刪除事件。

非公平鎖:直接嘗試在指定path下創建節點,創建成功,則說明該節點搶到鎖了。如果創建失敗,則監聽鎖節點的刪除事件,或者sleep一段時間后再重試。

可重入:使用ThreadLocal記錄加鎖客戶端的唯一標識。重復時先從ThreadLocal獲取,獲取到,這認為加鎖成功,直接返回。

使用瞬時節點創建可重入公平鎖

使用瞬時節點的好處是當Session失效時,該節點將被清理,從而避免使用持久節點加鎖成功后,拋異常或宕機或服務器重啟等原因造成的鎖無法釋放問題。

使用curator實現

// 假設需要加鎖的訂單Id
private static String orderId = "157146671409578219";
// 工程名
private static String appName = "trade_";
// 此次加鎖業務處理邏輯描述
private static String operatorDesc = "updateTrade_";

// 部門,每個部門可以有自己的zk空間目錄
private static String department = "zfpt" ;

// 鎖前綴 應該根據業務 具有唯一性
private static String lockPrefixKey = "/" + appName + operatorDesc ;

/**
 * 每個線程 創建自己的Connection ,創建自己的Session
 */
@Test
public void curator() throws Exception {

    for (int i = 0 ; i < 100 ; i++) {

        Thread currentThread = new Thread(() -> {

            // 創建Connection
            CuratorFramework client = CuratorFrameworkFactory.builder()
                    .connectString("master:2181,slave1:2181,slave2:2181")
                    .retryPolicy(new RetryOneTime(1000)) //重試策略
                    .namespace(department) // 可以設置自己部門縮寫
                    .build();
            client.start();

            // 模擬對同一個訂單加鎖
            InterProcessMutex lock = new InterProcessMutex(client, lockPrefixKey + orderId);

            try {
                // 一直嘗試加鎖 直到鎖可用。 有點像synchronized
                // lock.acquire();
                if(lock.acquire(1, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + " 搶到鎖");
                } else {
                    System.out.println(Thread.currentThread().getName() + "超時沒有搶到鎖");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    // 如果當前線程獲得到鎖,則釋放鎖
                    if(lock.isAcquiredInThisProcess()) {
                        System.out.println(Thread.currentThread().getName() + " 釋放鎖");
                        lock.release();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 沒有搶到鎖,故沒有釋放鎖");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        currentThread.setName("Lock【" + i + "】");
        currentThread.start();
    }

    Thread.sleep(Integer.MAX_VALUE);
}

實現原理:
因為ZK不允許在臨時節點下創建子節點,所以InterProcessMutex工具類會根據加鎖傳入path的(也就是案例中的lockPrefixKey + orderId)創建一個持久節點;然后在這個持久節點下創建瞬時節點,創建成功后,對該持久節點下的全部子節點進行降序排序,判斷當前節點是否是第一個節點,如果是則獲取鎖,否則對前一個節點加上監聽事件。然后Object.wait(),當監聽事件被觸發,則會調用notifyAll方法,對等待線程進行喚醒。再次嘗試獲取鎖。

缺點:

  1. 傳入的加鎖節點會被創建為永久節點(就是lockPrefixKey + orderId),這樣zk節點數量將會急速遞增。
  2. 臨時節點不穩定:一個客戶端加鎖成功后,可能會因為網絡抖動等原因導致Session斷開,該客戶端創建的臨時節點被清理,導致另外一節正在監聽的客戶端加鎖成功,同時進行操作。

使用持久節點創建可重入公平鎖

上文提到的臨時節點不穩定,父節點為永久節點無法釋放問題。我的拙見是:

  1. 使用持久節點代替臨時節點:釋放鎖的時候刪除自己創建的加鎖節點。
  2. 父節點為永久節點無法釋放:可以在每個客戶端釋放鎖的時候進行判斷,如果自己是最后一個節點,則刪除父節點。
  3. 但是需要考慮的問題:客戶端加鎖成功后,宕機或重啟或者其他極端異常情況,無法刪除加鎖節點。最后一個加鎖節點同樣異常,也無法刪除父節點。這時,可以給每個鎖加過期時間,過期失效。由於zk沒有提供過期自動清理,所以可以在第二次訪問該節點的時候 先進行判斷,判斷失效先刪除再創建。如果沒有第二次訪問的節點 可以依靠定時任務進行節點清理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM