Zookeeper分布式鎖

前言

分布式鎖,在實(shí)際的業(yè)務(wù)使用場景中算是比較常用的了,而分布式鎖的實(shí)現(xiàn),常見的除了redis之外,就是zk的實(shí)現(xiàn)了;
下面簡單的創(chuàng)建一個(gè)分布式鎖。

依賴

<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

實(shí)例創(chuàng)建

public class ZkLock implements Watcher {

    private ZooKeeper zooKeeper;
    // 創(chuàng)建一個(gè)持久的節(jié)點(diǎn),作為分布式鎖的根目錄
    private String root;

    public ZkLock(String root) throws IOException {
        try {
            this.root = root;
            zooKeeper = new ZooKeeper("127.0.0.1:2181", 500_000, this);
            Stat stat = zooKeeper.exists(root, false);
            if (stat == null) {
                // 不存在則創(chuàng)建
                createNode(root, true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    // 簡單的封裝節(jié)點(diǎn)創(chuàng)建,這里只考慮持久 + 臨時(shí)順序
    private String createNode(String path, boolean persistent) throws Exception {
        return zooKeeper.create(path, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, persistent ? CreateMode.PERSISTENT : CreateMode.EPHEMERAL_SEQUENTIAL);
    }
}

需要持有當(dāng)前節(jié)點(diǎn)和監(jiān)聽前一個(gè)節(jié)點(diǎn)的變更,所以我們?cè)赯kLock實(shí)例中,添加兩個(gè)成員;

/**
 * 當(dāng)前節(jié)點(diǎn)
 */
private String current;

/**
 * 前一個(gè)節(jié)點(diǎn)
 */
private String pre;

嘗試獲取鎖的邏輯

  • current不存在,在表示沒有創(chuàng)建過,就創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn),并賦值current
  • current存在,則表示之前已經(jīng)創(chuàng)建過了,目前處于等待鎖釋放過程
  • 接下來根據(jù)當(dāng)前節(jié)點(diǎn)順序是否最小,來表明是否持有鎖成功
  • 當(dāng)順序不是最小時(shí),找前面那個(gè)節(jié)點(diǎn),并賦值 pre
  • 監(jiān)聽pre的變化
/**
 * 嘗試獲取鎖,創(chuàng)建順序臨時(shí)節(jié)點(diǎn),若數(shù)據(jù)最小,則表示搶占鎖成功;否則失敗
 *
 * @return
 */
public boolean tryLock() {
    try {
        String path = root + "/";
        if (current == null) {
            // 創(chuàng)建臨時(shí)順序節(jié)點(diǎn)
            current = createNode(path, false);
        }
        List<String> list = zooKeeper.getChildren(root, false);
        Collections.sort(list);

        if (current.equalsIgnoreCase(path + list.get(0))) {
            // 獲取鎖成功
            return true;
        } else {
            // 獲取鎖失敗,找到前一個(gè)節(jié)點(diǎn)
            int index = Collections.binarySearch(list, current.substring(path.length()));
            // 查詢當(dāng)前節(jié)點(diǎn)前面的那個(gè)
            pre = path + list.get(index - 1);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

注意上面的實(shí)現(xiàn),這里并沒有去監(jiān)聽前一個(gè)節(jié)點(diǎn)的變更,在設(shè)計(jì)tryLock,因?yàn)槭橇ⅠR返回成功or失敗,所以使用這個(gè)接口的,不需要注冊(cè)監(jiān)聽。

我們的監(jiān)聽邏輯,放在 lock() 同步阻塞里面;

  1. 嘗試搶占鎖,成功則直接返回
  2. 拿鎖失敗,則監(jiān)聽前一個(gè)節(jié)點(diǎn)的刪除事件
public boolean lock() {
    if (tryLock()) {
        return true;
    }

    try {
        // 監(jiān)聽前一個(gè)節(jié)點(diǎn)的刪除事件
        Stat state = zooKeeper.exists(pre, true);
        if (state != null) {
            synchronized (pre) {
                // 阻塞等待前面的節(jié)點(diǎn)釋放
                pre.wait();
                // 這里不直接返回true,因?yàn)榍懊娴囊粋€(gè)節(jié)點(diǎn)刪除,可能并不是因?yàn)樗钟墟i并釋放鎖,如果是因?yàn)檫@個(gè)會(huì)話中斷導(dǎo)致臨時(shí)節(jié)點(diǎn)刪除,這個(gè)時(shí)候需要做的是換一下監(jiān)聽的 preNode
                return lock();
            }
        } else {
          // 不存在,則再次嘗試拿鎖
          return lock();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

注意:

  • 當(dāng)節(jié)點(diǎn)不存在時(shí),或者事件觸發(fā)回調(diào)之后,重新調(diào)用lock(),表明我胡漢三又來競爭鎖了?
    為啥不是直接返回 true? 而是需要重新競爭呢?
  • 因?yàn)榍懊婀?jié)點(diǎn)的刪除,有可能是因?yàn)榍懊婀?jié)點(diǎn)的會(huì)話中斷導(dǎo)致的;但是鎖還在另外的實(shí)例手中,這個(gè)時(shí)候我應(yīng)該做的是重新排隊(duì)

最后別忘了釋放鎖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容