Zookeeper分布式鎖

前言

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

依賴

<!-- 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)建一個持久的節(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)建悼枢,這里只考慮持久 + 臨時順序
    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)聽前一個節(jié)點(diǎn)的變更,所以我們在ZkLock實(shí)例中脾拆,添加兩個成員馒索;

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

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

嘗試獲取鎖的邏輯

  • current不存在,在表示沒有創(chuàng)建過名船,就創(chuàng)建一個臨時順序節(jié)點(diǎn)绰上,并賦值current
  • current存在,則表示之前已經(jīng)創(chuàng)建過了渠驼,目前處于等待鎖釋放過程
  • 接下來根據(jù)當(dāng)前節(jié)點(diǎn)順序是否最小蜈块,來表明是否持有鎖成功
  • 當(dāng)順序不是最小時,找前面那個節(jié)點(diǎn)迷扇,并賦值 pre
  • 監(jiān)聽pre的變化
/**
 * 嘗試獲取鎖百揭,創(chuàng)建順序臨時節(jié)點(diǎn),若數(shù)據(jù)最小蜓席,則表示搶占鎖成功器一;否則失敗
 *
 * @return
 */
public boolean tryLock() {
    try {
        String path = root + "/";
        if (current == null) {
            // 創(chuàng)建臨時順序節(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 {
            // 獲取鎖失敗,找到前一個節(jié)點(diǎn)
            int index = Collections.binarySearch(list, current.substring(path.length()));
            // 查詢當(dāng)前節(jié)點(diǎn)前面的那個
            pre = path + list.get(index - 1);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

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

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

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

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

注意:

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

最后別忘了釋放鎖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末送爸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子暖释,更是在濱河造成了極大的恐慌袭厂,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件球匕,死亡現(xiàn)場離奇詭異纹磺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亮曹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門橄杨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人照卦,你說我怎么就攤上這事式矫。” “怎么了窄瘟?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵衷佃,是天一觀的道長趟卸。 經(jīng)常有香客問我蹄葱,道長,這世上最難降的妖魔是什么锄列? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任图云,我火速辦了婚禮,結(jié)果婚禮上邻邮,老公的妹妹穿的比我還像新娘竣况。我一直安慰自己,他們只是感情好筒严,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布丹泉。 她就那樣靜靜地躺著情萤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摹恨。 梳的紋絲不亂的頭發(fā)上筋岛,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音晒哄,去河邊找鬼睁宰。 笑死,一個胖子當(dāng)著我的面吹牛寝凌,可吹牛的內(nèi)容都是我干的柒傻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼较木,長吁一口氣:“原來是場噩夢啊……” “哼红符!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起劫映,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤违孝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后泳赋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雌桑,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年祖今,在試婚紗的時候發(fā)現(xiàn)自己被綠了校坑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡千诬,死狀恐怖耍目,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徐绑,我是刑警寧澤邪驮,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站傲茄,受9級特大地震影響毅访,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盘榨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一喻粹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧草巡,春花似錦守呜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弥喉。三九已至,卻和暖如春玛迄,著一層夾襖步出監(jiān)牢的瞬間档桃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工憔晒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藻肄,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓拒担,卻偏偏與公主長得像嘹屯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子从撼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容