Zookeeper實現(xiàn)分布式鎖(三)FairLock

在Zookeeper實現(xiàn)分布式鎖的前兩篇文章

Zookeeper實現(xiàn)分布式鎖(一)While版
Zookeeper實現(xiàn)分布式鎖(二)Watcher版

我們實現(xiàn)了兩種類型的鎖,第一種是while循環(huán),由于無限循環(huán)帶來的cpu和zookeeper服務(wù)器的消耗,我們使用了watcher的方式,watcher方式采用了客戶端監(jiān)聽鎖節(jié)點的方式,完美解決了while出現(xiàn)的問題,但是這種方式又會存在“驚群”效應(yīng),本篇文章我們來實現(xiàn)一種排隊鎖也就是公平鎖。

Zookeeper的節(jié)點類型

zookeeper實現(xiàn)公平鎖依賴于zookeeper的順序節(jié)點,先來了解一下zookeeper的節(jié)點都有什么類型的節(jié)點。

97Z0@F$BP%S)Y1W07WZK`41.png

一共有四種

EPHEMERAL:臨時節(jié)點,當(dāng)客戶端與ZooKeeper集合斷開連接時,臨時節(jié)點會自動刪除。
PERSISTENT:持久節(jié)點,即使在創(chuàng)建該節(jié)點的客戶端斷開連接后,持久節(jié)點仍然存在。
EPHEMERAL_SEQUENTIAL:臨時順序節(jié)點
PERSISTENT_SEQUENTIAL:持久順序節(jié)點

重點說一下順序節(jié)點的含義:順序節(jié)點可以是持久的或臨時的。當(dāng)一個新的znode被創(chuàng)建為一個順序節(jié)點時,ZooKeeper通過將10位的序列號附加到原始名稱來設(shè)置znode的路徑。例如,如果將具有路徑 /myapp 的znode創(chuàng)建為順序節(jié)點,則ZooKeeper會將路徑更改為 /myapp0000000001 ,并將下一個序列號設(shè)置為0000000002。如果兩個順序節(jié)點是同時創(chuàng)建的,那么ZooKeeper不會對每個znode使用相同的數(shù)字。

根據(jù)同時創(chuàng)建的同名順序節(jié)點zookeeper會自動在命名上排序的特性,可以實現(xiàn)我們的公平鎖。

FairLock實現(xiàn)

1.獲取鎖

多個客戶端在lockName目錄下創(chuàng)建同名的臨時順序節(jié)點,因為zookeeper會為我們保證節(jié)點的命名不重復(fù)性和順序性,所以可以利用節(jié)點的順序進(jìn)行鎖的判斷。

public void lock(){
            String path = null;
            try {
                path = zk.create(lockName+"/mylock_", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                lockZnode = path;
                List<String> minPath = zk.getChildren(lockName,false);
                System.out.println(minPath);
                Collections.sort(minPath);
                System.out.println("最小的節(jié)點是:"+minPath.get(0));
                if (path!=null&&!path.isEmpty()
                        &&minPath.get(0)!=null&&!minPath.get(0).isEmpty()
                        &&path.equals(lockName+"/"+minPath.get(0))) {
                    System.out.println(Thread.currentThread().getName() + "  獲取鎖...");
                    return;
                }
                String watchNode = null;
                for (int i=minPath.size()-1;i>=0;i--){
                    if(minPath.get(i).compareTo(path.substring(path.lastIndexOf("/") + 1))<0){
                        watchNode = minPath.get(i);
                        break;
                    }
                }

                if (watchNode!=null){
                    final String watchNodeTmp = watchNode;
                    final Thread thread = Thread.currentThread();
                    Stat stat = zk.exists(lockName + "/" + watchNodeTmp,new Watcher() {
                        @Override
                        public void process(WatchedEvent watchedEvent) {
                            if(watchedEvent.getType() == Event.EventType.NodeDeleted){
                                System.out.println("delete事件來了");
                                thread.interrupt();
                                System.out.println("打斷當(dāng)前線程");
                            }
                        }
                    });
                    if(stat != null){
                        System.out.println(Thread.currentThread().getName() + " waiting for " + lockName + "/" + watchNode);
                    }
                }
                try {
                    Thread.sleep(10000);
                }catch (InterruptedException ex){
                    System.out.println(Thread.currentThread().getName() + " 被喚醒");
                    System.out.println(Thread.currentThread().getName() + "  獲取鎖...");
                    return;
                }

            } catch (Exception e) {
               e.printStackTrace();
            }
}

獲取鎖邏輯:

1.首先創(chuàng)建順序節(jié)點,然后獲取當(dāng)前目錄下最小的節(jié)點,判斷最小節(jié)點是不是當(dāng)前節(jié)點,如果是那么獲取鎖成功,如果不是那么獲取鎖失敗。
2.獲取鎖失敗的節(jié)點獲取當(dāng)前節(jié)點上一個順序節(jié)點,對此節(jié)點注冊watcher監(jiān)聽,并使當(dāng)前線程進(jìn)入sleep狀態(tài)。
3.當(dāng)監(jiān)聽的節(jié)點unlock刪除節(jié)點之后會捕獲到delete事件,這說明前面的線程都執(zhí)行完了,當(dāng)前線程interrupt,打斷sleep狀態(tài),獲取鎖。

2.釋放鎖
public void unlock(){
        try {
            System.out.println(Thread.currentThread().getName() +  "釋放 Lock...");
            zk.delete(lockZnode,-1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
3.輸出結(jié)果
Receive event WatchedEvent state:SyncConnected type:None path:null
connection is ok
[mylock_0000000112]
最小的節(jié)點是:mylock_0000000112
pool-1-thread-2  獲取鎖...
Receive event WatchedEvent state:SyncConnected type:None path:null
connection is ok
Receive event WatchedEvent state:SyncConnected type:None path:null
connection is ok
Receive event WatchedEvent state:SyncConnected type:None path:null
connection is ok
[mylock_0000000113, mylock_0000000112, mylock_0000000115, mylock_0000000114]
最小的節(jié)點是:mylock_0000000112
[mylock_0000000113, mylock_0000000112, mylock_0000000115, mylock_0000000114]
最小的節(jié)點是:mylock_0000000112
[mylock_0000000113, mylock_0000000112, mylock_0000000115, mylock_0000000114]
最小的節(jié)點是:mylock_0000000112
pool-1-thread-2釋放 Lock...
pool-1-thread-1 waiting for /mylock/mylock_0000000113
pool-1-thread-3 waiting for /mylock/mylock_0000000114
pool-1-thread-4 waiting for /mylock/mylock_0000000112
delete事件來了
打斷當(dāng)前線程
pool-1-thread-4 被喚醒
pool-1-thread-4  獲取鎖...
pool-1-thread-4釋放 Lock...
delete事件來了
打斷當(dāng)前線程
pool-1-thread-1 被喚醒
pool-1-thread-1  獲取鎖...
pool-1-thread-1釋放 Lock...
delete事件來了
打斷當(dāng)前線程
pool-1-thread-3 被喚醒
pool-1-thread-3  獲取鎖...
pool-1-thread-3釋放 Lock...

總結(jié)

根據(jù)輸出結(jié)果,可以看出來這次的客戶端獲取鎖是公平的,排著隊一個一個來的,因為每個節(jié)點都有自己的一個數(shù)字id,根據(jù)數(shù)字id來監(jiān)聽比自己小的節(jié)點,這樣釋放鎖只喚醒一個客戶端,而不會產(chǎn)生驚群效應(yīng)。

while版、watcher版、FairLock版這三種鎖
while:性能好,但是資源消耗大,適合業(yè)務(wù)邏輯時間短的場景
watcher:性能中上,但是容易驚群,適合客戶端比較少的場景
FairLock:性能低,但是安全性高,適合速度要求一般,按順序來的場景

githup代碼:https://github.com/granett/zookeeper/blob/master/src/main/java/zookeeper/zookeeper/Zookeeper_3_FairLock.java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,604評論 2 380

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