1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        坐下坐下,基本操作(ZooKeeper 操作篇)

        共 11569字,需瀏覽 24分鐘

         ·

        2021-02-06 19:07

        本文作者:HelloGitHub-老荀

        Hi,這里是 HelloGitHub 推出的 HelloZooKeeper 系列,免費(fèi)開源、有趣、入門級的 ZooKeeper 教程,面向有編程基礎(chǔ)的新手。

        ZooKeeper 是 Apache 軟件基金會的一個軟件項(xiàng)目,它為大型分布式計(jì)算提供開源的分布式配置服務(wù)、同步服務(wù)和命名注冊。ZooKeeper 曾經(jīng)是 Hadoop 的一個子項(xiàng)目,但現(xiàn)在是一個頂級獨(dú)立的開源項(xiàng)目。

        ZK 在實(shí)際開發(fā)工作中經(jīng)常會用到,算的上是吃飯的家伙了,那可得玩透、用的趁手,要不怎么進(jìn)階和升職加薪呢?來和 HelloGitHub 一起學(xué)起來吧~

        本系列教程是從零開始講解 ZooKeeper,內(nèi)容從最基礎(chǔ)的安裝使用到背后原理和源碼的講解,整個系列希望通過有趣文字、詼諧的氣氛中讓 ZK 的知識“鉆”進(jìn)你聰明的大腦。本教程是開放式:開源、協(xié)作,所以不管你是新手還是老司機(jī),我們都希望你可以加入到本教程的貢獻(xiàn)中,一起讓這個教程變得更好

        • 新手:參與修改文中的錯字、病句、拼寫、排版等問題
        • 使用者:參與到內(nèi)容的討論和問題解答、幫助其他人的事情
        • 老司機(jī):參與到文章的編寫中,讓你的名字出現(xiàn)在作者一欄

        項(xiàng)目地址:https://github.com/HelloGitHub-Team/HelloZooKeeper

        今天我們會講解下,如何使用 Java 代碼客戶端去操作 ZK。

        一、基本操作

        1.1 馬果果的新規(guī)定

        老規(guī)矩,在開始實(shí)戰(zhàn)之前呢,我還是講一個小故事(故事中的人物,純屬虛構(gòu),請勿對號入座,如有雷同,純屬巧合)。

        馬果果自從擔(dān)任了辦事處的負(fù)責(zé)人后,每天那是忙的不可開交,村民有事都來找他,他的小本子上已經(jīng)密密麻麻記了一大堆:

        特別是雞太美,儼然已經(jīng)成為了日更 UP 主,每天的頻繁更新讓馬果果倍感力不從心,他想,如果再這樣毫無章法的記下去,不但以后自己會越來越累,等自己退休后,別人來交接也會無從下手,那還不得在背后說我管理不當(dāng),對著我指指點(diǎn)點(diǎn)。要晚節(jié)不保啊,到時候怕不是要給全村人民謝罪。

        于是辦事處出臺了新的規(guī)定,每次過來登記的村民必須對自己要登記的事務(wù)進(jìn)行分類,而馬果果則根據(jù)這些分類去進(jìn)行記錄,所以馬果果的筆記(以下簡稱:小紅本)就變成了這樣:

        但是執(zhí)行了規(guī)定一段時間以后,以雞太美馬小云為首的村民代表又向馬果果提出了:“我們都是老熟人了,每次來都得自報家門,能不能做點(diǎn)便民措施?你這辦事處的宗旨難道不就是服務(wù)咱人民群眾的嗎?”

        馬果果聽完也覺得很有道理,于是給每一個老熟人都創(chuàng)建了一個標(biāo)簽。比如以后雞太美過來創(chuàng)建的記錄,都直接放到雞太美的標(biāo)簽下,這樣雞太美只需要關(guān)心自己具體想要記哪些東西就行了,所以筆記本最后變成了這樣:

        而對于需要接收到通知到村民也是一樣,馬果果會在需要通知的事務(wù)旁備注下,比如雞太美的頭號粉絲坤坤,對雞太美的跳舞視頻十分感興趣,所以在雞太美的跳舞事務(wù)旁備注下:

        然后拿出另一本本子(以下簡稱:小黃本)把需要通知誰給記下來:

        隨著時間推移雞太美的人氣與日俱增,現(xiàn)在連馬小云東東都成了她的粉絲,紛紛都要關(guān)注她的更新:

        所以現(xiàn)在馬果果當(dāng)記錄完小紅本后,會看看當(dāng)前的事務(wù)是不是有別人訂閱了通知,如果有的話,會再拿出小黃本去找到對應(yīng)需要通知的村民,一個個打電話通知他們。

        馬果果對自己的這次出臺的規(guī)定非常滿意,當(dāng)面對記者采訪的時候得意的說道,這是自己退休后堅(jiān)持學(xué)習(xí)計(jì)算機(jī),從計(jì)算機(jī)的文件目錄中得到的靈感,人果然還是要「活到老學(xué)到老」啊!


        小故事講完了,下面用猿話翻譯一下:

        ZK 定義了每一個記錄必須有一個對應(yīng)路徑,這個路徑就是對應(yīng)小故事中的辦事處規(guī)定的分類,而整個記錄的結(jié)構(gòu)的確和 Linux 中的文件樹類似,有一個根節(jié)點(diǎn) /,節(jié)點(diǎn)間有父子關(guān)系,路徑用 / 分割,比如:

        /雞太美/更新視頻/跳舞/20201101

        而故事中的標(biāo)簽,其實(shí)就是客戶端中指定的 chroot,實(shí)際上是由客戶端維護(hù)的,服務(wù)端并不知道。

        1.2 代碼實(shí)戰(zhàn)

        特別說明接下來的實(shí)戰(zhàn)是用官方的 Java 客戶端作為演示的,新建一個空白的 Maven 項(xiàng)目,然后引入 ZK 的依賴:

        <dependency>
        ??<groupId>org.apache.zookeepergroupId>
        ??<artifactId>zookeeperartifactId>
        ??<version>3.6.2version>
        dependency>

        要操作 ZK 首先得先創(chuàng)建一個客戶端對象,我們以雞太美為例

        ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181/雞太美",?3000,?null);

        ZooKeeper第一個字符串就是連接的服務(wù)端地址,/ 后面就是 chroot, 就是小故事里的標(biāo)簽,之后該客戶端所有的操作都會以/雞太美作為頂層路徑去處理。

        最后當(dāng)客戶端退出的時候,記得要關(guān)閉客戶端噢

        client.close();

        1.2.1 創(chuàng)建路徑

        這里需要提醒的是,官方的客戶端是沒有遞歸創(chuàng)建的功能的,所以在創(chuàng)建多級路徑的時候,客戶端需要自己確保路徑中的父級節(jié)點(diǎn)是存在的!

        下面的方法,直接運(yùn)行是會報錯的,所以需要逐級創(chuàng)建 20201101 的父路徑,最終才能成功,這里主要是演示結(jié)構(gòu),而之后的 ZooDefs.Ids.OPEN_ACL_UNSAFE 是一種 ACL 的權(quán)限,意思就是不會進(jìn)行權(quán)限校驗(yàn),關(guān)于權(quán)限,之后會有篇幅介紹,這里直接跳過。

        client.create("/更新視頻/跳舞/20201101",?"這是Data,既可以記錄一些業(yè)務(wù)數(shù)據(jù)也可以隨便寫".getBytes(),?ZooDefs.Ids.OPEN_ACL_UNSAFE,?CreateMode.PERSISTENT);

        最后的 CreateMode.PERSISTENT 代表當(dāng)前節(jié)點(diǎn)是一個持久類型的節(jié)點(diǎn),3.6.2 中一共有 7 種類型,下面列出并且給出簡單解釋:

        PERSISTENT???????????//?持久節(jié)點(diǎn),一旦創(chuàng)建成功不會被刪除,除非客戶端主動發(fā)起刪除請求
        PERSISTENT_SEQUENTIAL??????//?持久順序節(jié)點(diǎn),會在用戶路徑后面拼接一個不會重復(fù)的字增數(shù)字后綴,其他同上
        EPHEMERAL????????????//?臨時節(jié)點(diǎn),當(dāng)創(chuàng)建該節(jié)點(diǎn)的客戶端鏈接斷開后自動被刪除
        EPHEMERAL_SEQUENTIAL??????//?臨時順序節(jié)點(diǎn),基本同上,也是增加一個數(shù)字后綴
        CONTAINER????????????//?容器節(jié)點(diǎn),一旦子節(jié)點(diǎn)被刪除完就會被服務(wù)端刪除
        PERSISTENT_WITH_TTL???????//?帶過期時間的持久節(jié)點(diǎn),帶有超時時間的節(jié)點(diǎn),如果超時時間內(nèi)沒有子節(jié)點(diǎn)被創(chuàng)建,就會被刪除
        PERSISTENT_SEQUENTIAL_WITH_TTL?//?帶過期時間的持久順序節(jié)點(diǎn),基本同上,多了一個數(shù)字后綴

        大家可能比較熟悉前四種,對后三種不太熟悉,特別是最后兩種帶 TTL 的類型,這兩種類型在 ZK 默認(rèn)配置下還是不支持的,需要在 zoo.cfg 配置中添加 extendedTypesEnabled=true 啟用擴(kuò)展功能,否則的話就會收到 Unimplemented for 的錯誤。

        示例中路徑創(chuàng)建完就會是這樣:

        雞太美
        ?|--更新視頻
        ???|--跳舞
        ?????|--20201101

        1.2.2 刪除路徑

        官方的客戶端也不支持遞歸刪除,需要確保刪除的節(jié)點(diǎn)是葉子節(jié)點(diǎn),否則就會收到錯誤,我們這里把 20201101 給刪除:

        client.delete("/更新視頻/跳舞/20201101",?-1);

        -1 是一個 version 字段,相當(dāng)于 ZK 提供的樂觀鎖機(jī)制,如果是 -1 的話就是無視節(jié)點(diǎn)的版本信息。

        刪除完就是這樣:

        雞太美
        ?|--更新視頻
        ???|--跳舞

        1.2.3 設(shè)置數(shù)據(jù)

        每一個節(jié)點(diǎn)都可以擁有自己的數(shù)據(jù),既可以通過創(chuàng)建的時候指定,也可以在之后通過設(shè)置的方式指定。

        client.setData("/更新視頻/跳舞",?"這是Data,可以寫一些關(guān)于業(yè)務(wù)的參數(shù)".getBytes(),?-1);

        -1 的含義和刪除路徑中是一樣的,也是無視版本信息。

        1.2.4 判斷路徑是否存在

        由于創(chuàng)建和刪除都不支持遞歸,所以需要對目標(biāo)路徑進(jìn)行判斷是否存在來決定是否進(jìn)行下一步

        Stat?stat?=?client.exists("/更新視頻",?false);
        System.out.println(stat?!=?null???"存在"?:?"不存在");?//?存在

        false 意思是不進(jìn)行訂閱,關(guān)于訂閱之后會一起說。

        1.2.5 獲取數(shù)據(jù)

        能設(shè)置數(shù)據(jù),必然也能獲取數(shù)據(jù),所以 ZK 可以偶爾客串一下數(shù)據(jù)存儲的角色

        byte[]?data?=?client.getData("/更新視頻/跳舞",?false,?null);
        System.out.println(new?String(data));?//?這是Data,可以寫一些關(guān)于業(yè)務(wù)的參數(shù)

        1.2.6 獲取子節(jié)點(diǎn)列表

        前面說了 ZK 是一個樹形的結(jié)構(gòu),有父子節(jié)點(diǎn)概念,所以可以查詢某一個節(jié)點(diǎn)下面的所有子節(jié)點(diǎn)

        List?children?=?client.getChildren("/更新視頻",?false);
        System.out.println(children);?//?[跳舞]

        1.2.7 設(shè)置訂閱

        上面介紹的三個方法:判斷路徑是否存在、獲取數(shù)據(jù)、獲取子節(jié)點(diǎn)列表,這三種方法(包括他們的重載方法),都可以對路徑進(jìn)行訂閱,訂閱的方式有兩種:

        • 傳遞一個 boolean 值,如果使用此方式的話,回調(diào)對象就是創(chuàng)建 ZooKeeper 時的第三個參數(shù) defaultWatcher,只不過之前示例中是 null
        • 直接在方法中傳入一個 Watcher 的實(shí)現(xiàn)類,此實(shí)現(xiàn)類會作為此路徑之后的回調(diào)對象(推薦)

        下面分別演示下:

        //?方式1
        ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181/雞太美",?3000,?new?Watcher()?{
        ??//?這個就是?defaultWatcher?參數(shù),是當(dāng)前客戶端默認(rèn)的回調(diào)實(shí)現(xiàn)
        ??@Override
        ??public?void?process(WatchedEvent?event)?{
        ????System.out.println("這是本客戶端全局的默認(rèn)回調(diào)對象");
        ?}
        });
        //?exists
        client.exists("/更新視頻",?true);
        //?getData
        client.getData("/更新視頻/跳舞",?true,?null);
        //?getChildren
        client.getChildren("/更新視頻",?true);
        //?方式2
        //?exists
        client.exists("/更新視頻",?new?Watcher()?{
        ??@Override
        ??public?void?process(WatchedEvent?event)?{
        ????System.out.println("我是回調(diào)對象的實(shí)現(xiàn)");
        ??}
        });
        //?getData
        client.getData("/更新視頻/跳舞",?new?Watcher()?{
        ??@Override
        ??public?void?process(WatchedEvent?event)?{
        ????System.out.println("我是回調(diào)對象的實(shí)現(xiàn)");
        ??}
        },?null);
        //?getChildren
        client.getChildren("/更新視頻",?new?Watcher()?{
        ??@Override
        ??public?void?process(WatchedEvent?event)?{
        ????System.out.println("我是回調(diào)對象的實(shí)現(xiàn)");?
        ??}
        });

        至于回調(diào)是怎么每次能觸發(fā)到對應(yīng)的方法的,這里就賣個關(guān)子,之后會有文章詳細(xì)解釋。

        關(guān)于 ZK 客戶端的操作大致就這么幾種,限于篇幅我也無法一一舉例,本系列文章目的也不是作為官方文檔的翻譯,重要的還是能激發(fā)出大家對于技術(shù)的熱情,剩下的那些使用情況就當(dāng)我給大家的課后練習(xí)題吧~關(guān)于 ZK 的基本操作就講完了。

        二、進(jìn)階操作

        整完了基本操作,咱們再來整點(diǎn)高級的。

        2.1 雞太美的簽售會

        我們繼續(xù)先說說動物村發(fā)生的故事。

        隨著直播的人氣和關(guān)注數(shù)的日益增長,雞太美儼然已經(jīng)成為了動物村的大明星,都出專輯了,所以準(zhǔn)備回饋下粉絲辦場簽售會。

        決定在馬果果的辦事處前布置場地,由身強(qiáng)體壯的太極宗師馬果果擔(dān)任保安保證現(xiàn)場的秩序

        馬果果說了想要進(jìn)去和雞太美一對一粉絲見面會的,需要拿走我手中的憑證,在簽完名后趕緊出來,還要把這個憑證歸還給我。

        對不起,放錯圖了

        雞太美的粉絲們聽到可以和明星一對一見面,都瘋了,都火急火燎的趕到了辦事處的門口,不知道誰在人群中大喊了一聲:“搶??!先到先得??!”,都像餓虎撲食一般把馬果果撲倒在地,場面相當(dāng)混亂!

        最后是由雞太美的鐵桿粉絲坤坤拔得頭籌,搶下了馬果果手中的唯一憑證,換到了和明星偶像一對一的機(jī)會

        坤坤捧著手中心愛的專輯,心滿意足的回去了。

        重新拿回憑證的馬果果,看著眼前這一群餓狼

        頓時明白了自己接下來要面對的...

        ( 四小時以后 )

        終于,所有的粉絲都拿著手中還熱乎的專輯高高興興的回家去了。忙碌了一天的馬果果心想下次可不能這樣,要不是我老當(dāng)益壯怕不是要被抬進(jìn)醫(yī)院!

        2.2 雞太美的演唱會

        雞太美的粉絲數(shù)量終于突破了 100w !是時候找個理由再營銷自己一波了,于是就和經(jīng)紀(jì)公司商量能不能辦一個演唱會,現(xiàn)場賣票,既可以為自己造勢也可以滿足下粉絲見面的要求。這次同樣的找到了馬果果,希望馬果果能繼續(xù)幫忙組織下現(xiàn)場的秩序,高風(fēng)亮節(jié)的馬果果本來不想再接這些雜活了,但是聽到了經(jīng)紀(jì)公司開出的價錢后...

        但是自己畢竟年事已高,可經(jīng)不起上次的那樣折騰了,于是決定這次出臺一個新的規(guī)定來應(yīng)對之后粉絲瘋狂的行為,新場地布置成這樣:

        每一個粉絲都得先去馬果果那里拿一個從小到大的號碼,馬果果每次從 1 開始發(fā),一邊發(fā)一邊還要叫,從最小的號碼開始叫,叫到號碼的粉絲才能進(jìn)售票處買票,每一個粉絲拿到號之后就要關(guān)注下排在自己前面一位的情況,如果他買好了,自己趕緊要準(zhǔn)備起來,因?yàn)橄乱粋€就會輪到自己。

        就這樣,整個售賣現(xiàn)場井井有條,大家紛紛都夸獎馬果果高超的管理技巧。


        小故事又又講完了,下面用猿話翻譯一下:

        這兩個小故事講的就是 ZK 分布式鎖的大致原理,并且基本對應(yīng)了非公平鎖和公平鎖的兩種情況,雖然實(shí)際情況和故事中會有出入,但是通過故事希望給大家能有一個感性的認(rèn)識。

        非公平鎖的缺點(diǎn)在故事中也體現(xiàn)了,就是當(dāng)前一個持有鎖的進(jìn)程釋放之后,其他所有等待鎖的進(jìn)程都會被通知,這個就是經(jīng)常在面試題中提到的“羊群效應(yīng)”,從而再去爭搶該鎖,但是因?yàn)橛种挥幸粋€進(jìn)程能搶到鎖,其他的進(jìn)程會重新繼續(xù)等待循環(huán)下去,所以在應(yīng)對高并發(fā)場景的情況下該方案有較嚴(yán)重的性能問題,極大的增大了服務(wù)端的壓力。

        而公平鎖的話,每一個沒有獲取到鎖的進(jìn)程往往只需要關(guān)心排在它的前一個進(jìn)程的情況,每次也只有一個進(jìn)程會被喚醒,所以如果采用 ZK 作為分布式鎖的中間件的話,建議采用公平鎖的方式。

        2.3 代碼實(shí)戰(zhàn)

        下面用簡單的(偽)代碼演示下,如何使用 ZK 來編寫分布式鎖的邏輯

        2.3.1 非公平鎖

        假設(shè)我現(xiàn)在要鎖的對象是雞太美的演唱會

        ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null);
        try?{
        ??//?之前有提過必須保證?雞太美?的路徑存在
        ??client.create("/雞太美/演唱會",?"Data?沒有用隨便寫".getBytes(),?ZooDefs.Ids.OPEN_ACL_UNSAFE,?CreateMode.EPHEMERAL);
        ??//?創(chuàng)建成功的話就是獲取到鎖了,?之后執(zhí)行業(yè)務(wù)邏輯
        ??System.out.println("我是拿到鎖以后的業(yè)務(wù)邏輯");
        ??...
        ??//?處理完業(yè)務(wù)記得一定要刪除該節(jié)點(diǎn),表示釋放鎖,實(shí)際場景中這一步刪除應(yīng)該是在?finally?塊中
        ??client.delete("/雞太美/演唱會",?-1);
        }?catch?(KeeperException.NodeExistsException?e)?{
        ??//?如果報?NodeExistsException?就是沒獲取到鎖
        ??System.out.println("鎖被別人獲取了");
        ??//?對這個節(jié)點(diǎn)進(jìn)行監(jiān)聽
        ??client.exists("/雞太美/演唱會",?new?Watcher()?{
        ????@Override
        ????public?void?process(WatchedEvent?event)?{
        ??????if?(event.getType().equals(Event.EventType.NodeDeleted))?{
        ????????//?如果監(jiān)聽到了刪除事件就是上一個進(jìn)程釋放了鎖,?嘗試重新獲取鎖
        ????????//?這里就牽涉到這次再獲取失敗要繼續(xù)監(jiān)聽的遞歸過程,?其實(shí)需要一個封裝好的類似?lock?方法,偽代碼這里就不繼續(xù)演示了
        ????????client.create("/雞太美/演唱會",?"Data?沒有用隨便寫".getBytes(),?ZooDefs.Ids.OPEN_ACL_UNSAFE,?CreateMode.EPHEMERAL);
        ????????...
        ??????}
        ????}
        ??});
        }
        ...

        ZK 的非公平鎖用到了相同路徑無法重復(fù)創(chuàng)建加上臨時節(jié)點(diǎn)的特性,用臨時節(jié)點(diǎn)是因?yàn)槿绻?dāng)獲取鎖的進(jìn)程崩潰后,沒來得及釋放鎖的話會造成死鎖,但臨時節(jié)點(diǎn)會在客戶端的連接斷開后自動刪除,所以規(guī)避了死鎖的這個風(fēng)險。

        2.3.2 公平鎖

        同樣的演唱會,這次換成公平鎖來試試

        ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null);
        String?currentPath?=?client.create("/雞太美/演唱會",?"Data?沒有用隨便寫".getBytes(),?ZooDefs.Ids.OPEN_ACL_UNSAFE,?CreateMode.EPHEMERAL_SEQUENTIAL);
        //?因?yàn)橛行蛱柕拇嬖冢砸欢〞?chuàng)建成功
        //?然后就是獲取父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)的名稱
        List?children?=?client.getChildren("/雞太美",?false);
        //?先排序
        Collections.sort(children);
        if?(children.get(0).equals(currentPath))?{
        ??//?當(dāng)前路徑是最小的那個節(jié)點(diǎn),獲取鎖成功
        ??System.out.println("我是拿到鎖以后的業(yè)務(wù)邏輯");
        ??...
        ??//?同樣記得業(yè)務(wù)處理完一定要刪除該節(jié)點(diǎn)
        ??client.delete(currentPath,?-1);
        }?else?{
        ??//?不是最小節(jié)點(diǎn),獲取鎖失敗
        ??//?根據(jù)當(dāng)前節(jié)點(diǎn)路徑在所有子節(jié)點(diǎn)中獲取序號相比自己小?1?的那個節(jié)點(diǎn)
        ??String?preNode?=?getPreNode(currentPath,?children);
        ??//?對該節(jié)點(diǎn)進(jìn)行監(jiān)聽
        ??client.exists(preNode,?new?Watcher()?{
        ????@Override
        ????public?void?process(WatchedEvent?event)?{
        ??????if?(event.getType().equals(Event.EventType.NodeDeleted))?{
        ????????//?和非公平鎖一樣,再次嘗試獲取鎖,由于順序節(jié)點(diǎn)的緣故,所以此次獲取鎖應(yīng)該是不會失敗的
        ????????...
        ??????}
        ????}
        ??});
        }
        ...

        ZK 的公平鎖用到了臨時順序節(jié)點(diǎn),序號無法重復(fù)的特性,當(dāng)前的最小子節(jié)點(diǎn)才視為獲取鎖成功。

        2.3.3 Curator Recipes

        你們肯定會問上面這兩段代碼都沒法直接用,如果我想在項(xiàng)目中使用的話怎么辦呢?

        當(dāng)當(dāng)當(dāng)當(dāng)~這種活開源社區(qū)早就有人替我們干啦

        <dependency>
        ??<groupId>org.apache.curatorgroupId>
        ??<artifactId>curator-recipesartifactId>
        ??<version>5.1.0version>
        dependency>

        下面給出簡單例子

        RetryPolicy?retryPolicy?=?new?ExponentialBackoffRetry(1000,?3);
        CuratorFramework?client?=?CuratorFrameworkFactory.newClient("127.0.0.1:2181",?retryPolicy);
        client.start();
        InterProcessMutex?lock?=?new?InterProcessMutex(client,?"/lock");
        try?(Locker?locker?=?new?Locker(lock))?{
        ??//?使用?try-with-resources?語法糖自動釋放鎖
        ??System.out.println("獲取到鎖后的業(yè)務(wù)邏輯");
        }
        client.close();

        Curator 內(nèi)置了幾種鎖給我們使用,并且都可以通過 Locker 包裝使用

        • InterProcessMultiLock ? ?可以同時對幾個路徑加鎖,釋放也是同時的
        • InterProcessMutex ? ? ? 可重入排他鎖
        • InterProcessReadWriteLock ? ? ? ? 讀寫鎖
        • InterProcessSemaphoreMutex ? ? ? 不可重入排他鎖

        如果想看看優(yōu)秀的 ZK 分布式鎖如何寫的話,直接翻 curator-recipes 它的源碼吧~

        Curator 提供的還不止是分布式鎖,它還提供了分布式隊(duì)列,分布式計(jì)數(shù)器,分布式屏障,分布式原子類等,厲害吧~開源牛逼~

        2.3.4 和 Spring Boot 整合

        有沒有比上面 Curator 更簡單的呢?當(dāng)然!

        我已經(jīng)為你準(zhǔn)備好了一個示范項(xiàng)目:

        • 項(xiàng)目地址:https://github.com/HelloGitHub-Team/curator-springboot-demo

        事先說明,該項(xiàng)目僅僅只是用作演示如何將 Curator 整合進(jìn) Spring Boot,一切都是從簡配置,并且也只是演示了分布式鎖這一項(xiàng)功能,其他高級功能,如果有需要,讀者可以自行前往了解!

        常規(guī) Spring Boot 的依賴我就不展示了,我就列下和 Curator 相關(guān)的:

        項(xiàng)目使用 Maven 作為依賴管理工具

        <dependency>
        ??<groupId>org.springframework.bootgroupId>
        ??<artifactId>spring-boot-starter-integrationartifactId>
        dependency>
        <dependency>
        ??<groupId>org.springframework.integrationgroupId>
        ??<artifactId>spring-integration-zookeeperartifactId>
        dependency>

        一個 @Configuration 對象,用于創(chuàng)建對應(yīng)的 Bean

        @Configuration
        public?class?ZookeeperLockConfiguration?{
        ????@Value("${zookeeper.host:127.0.0.1:2181}")
        ????private?String?zkUrl;

        ????@Bean
        ????public?CuratorFrameworkFactoryBean?curatorFrameworkFactoryBean()?{
        ????????return?new?CuratorFrameworkFactoryBean(zkUrl);
        ????}

        ????@Bean
        ????public?ZookeeperLockRegistry?zookeeperLockRegistry(CuratorFramework?curatorFramework)?{

        ????????return?new?ZookeeperLockRegistry(curatorFramework,?"/HG-lock");
        ????}
        }

        兩個測試用的接口

        //?在需要使用鎖的?bean?中直接注入
        @Resource
        private?LockRegistry?lockRegistry;

        @GetMapping("/lock10")
        public?String?lock10()?{
        ??System.out.println("lock10?start?"?+?System.currentTimeMillis());
        ??final?Lock?lock?=?lockRegistry.obtain("lock");
        ??try?{
        ????lock.lock();
        ????System.out.println("lock10?get?lock?success?"?+?System.currentTimeMillis());
        ????TimeUnit.SECONDS.sleep(10);
        ??}?catch?(Exception?e)?{
        ??}?finally?{
        ????lock.unlock();
        ??}
        ??return?"OK";
        }

        @GetMapping("/immediate")
        public?String?immediate()?{
        ??System.out.println("immediate?start?"?+?System.currentTimeMillis());
        ??final?Lock?lock?=?lockRegistry.obtain("lock");
        ??try?{
        ????lock.lock();
        ????System.out.println("immediate?get?lock?success?"?+?System.currentTimeMillis());
        ??}?finally?{
        ????lock.unlock();
        ??}
        ??return?"immediate?return";
        }

        邏輯我稍微講一下,我是先調(diào)用 lock10 這個接口的,然后再調(diào)用 immediate 接口。lock10 這個接口獲取鎖后會 sleep 10 秒,而同時 immediate 也會嘗試獲取鎖,但是不會 sleep,假設(shè)分布式鎖有效的話,對應(yīng)的也會等 10 秒,所以可以從控制臺的時間戳看到兩個接口幾乎是同時請求的,但是獲取鎖的時間大概差了 10 秒,證明鎖有效。lockRegistryobtrain 方法字符串參數(shù)就是對應(yīng)的業(yè)務(wù)場景,例如:訂單號、用戶 ID 等,字符串相同的話就可以認(rèn)為是同一把鎖。

        另外有那么一點(diǎn)不嚴(yán)謹(jǐn)?shù)牡胤绞俏冶镜氐臏y試只啟動了一個 java 進(jìn)程,如果讀者需要測試分布式環(huán)境的話,只需要修改配置文件中的啟動端口,即可啟動多個進(jìn)程用來模擬分布式環(huán)境~

        lock10?start?1607417328823
        lock10?get?lock?success?1607417328855
        immediate?start?1607417329943
        immediate?get?lock?success?1607417338872

        而我在 sleep 的時間,去 ZK 上查了下發(fā)現(xiàn)框架會在我們指定的節(jié)點(diǎn)下創(chuàng)建兩個臨時節(jié)點(diǎn)來控制并發(fā),和我們之前演示的差不多,但具體的細(xì)節(jié)等待作為讀者的你去挖掘了~(或者自挖一坑?)

        /
        |--zookeeper
        |--HG-lock
        ??|--lock
        ?????|--_c_41d75f28-2346-4cf2-89e8-accccce9ad1a-lock-0000000000
        ?????|--_c_98549447-0ee4-4c93-8194-4ed428225f75-lock-0000000001

        更多關(guān)于示例的細(xì)節(jié)(其實(shí)也沒什么細(xì)節(jié),是個特別簡單的項(xiàng)目),可以直接訪問上面的項(xiàng)目地址查看源碼。

        三、總結(jié)

        本文使用故事和實(shí)戰(zhàn)講解了下,ZK 的基本操作和一部分進(jìn)階操作。下一篇就會進(jìn)入原理篇了,我會介紹 ZK 的服務(wù)端是如何處理每次的請求。



        關(guān)注公眾號第一時間收到推送


        ▼ 點(diǎn)擊?閱讀原文?點(diǎn)個 Star 吧

        瀏覽 30
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            少妇无码在线 | 曰曰操 | 欧美黄色录像片 | 五月丁香网站 | 亚洲欧美手机在线 | 五月丁香开心婷婷 | 91丨九色丨蝌蚪丨成人 | 黄色成人短视频 | 国产成人麻豆精品午夜在线 | 国产伊人自拍 |