<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Java分布式鎖看這篇就夠了

    共 14771字,需瀏覽 30分鐘

     ·

    2020-09-01 10:49

    點擊上方藍色字體,選擇“標星公眾號”

    優(yōu)質(zhì)文章,第一時間送達

    ? 作者?|??seesun2012

    來源 |? urlify.cn/uY36R3

    66套java從入門到精通實戰(zhàn)課程分享?

    什么是鎖?


    • 在單進程的系統(tǒng)中,當存在多個線程可以同時改變某個變量(可變共享變量)時,就需要對變量或代碼塊做同步,使其在修改這種變量時能夠線性執(zhí)行消除并發(fā)修改變量。

    • 而同步的本質(zhì)是通過鎖來實現(xiàn)的。為了實現(xiàn)多個線程在一個時刻同一個代碼塊只能有一個線程可執(zhí)行,那么需要在某個地方做個標記,這個標記必須每個線程都能看到,當標記不存在時可以設置該標記,其余后續(xù)線程發(fā)現(xiàn)已經(jīng)有標記了則等待擁有標記的線程結束同步代碼塊取消標記后再去嘗試設置標記。這個標記可以理解為鎖。

    • 不同地方實現(xiàn)鎖的方式也不一樣,只要能滿足所有線程都能看得到標記即可。如 Java 中 synchronize 是在對象頭設置標記,Lock 接口的實現(xiàn)類基本上都只是某一個 volitile 修飾的 int 型變量其保證每個線程都能擁有對該 int 的可見性和原子修改,linux 內(nèi)核中也是利用互斥量或信號量等內(nèi)存數(shù)據(jù)做標記。

    • 除了利用內(nèi)存數(shù)據(jù)做鎖其實任何互斥的都能做鎖(只考慮互斥情況),如流水表中流水號與時間結合做冪等校驗可以看作是一個不會釋放的鎖,或者使用某個文件是否存在作為鎖等。只需要滿足在對標記進行修改能保證原子性和內(nèi)存可見性即可。

    什么是分布式?


    分布式的 CAP 理論告訴我們:

    任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance),最多只能同時滿足兩項。

    目前很多大型網(wǎng)站及應用都是分布式部署的,分布式場景中的數(shù)據(jù)一致性問題一直是一個比較重要的話題?;?CAP理論,很多系統(tǒng)在設計之初就要對這三者做出取舍。在互聯(lián)網(wǎng)領域的絕大多數(shù)的場景中,都需要犧牲強一致性來換取系統(tǒng)的高可用性,系統(tǒng)往往只需要保證最終一致性。

    分布式場景


    此處主要指集群模式下,多個相同服務同時開啟.

    在許多的場景中,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。很多時候我們需要保證一個方法在同一時間內(nèi)只能被同一個線程執(zhí)行。在單機環(huán)境中,通過 Java 提供的并發(fā) API 我們可以解決,但是在分布式環(huán)境下,就沒有那么簡單啦。

    • 分布式與單機情況下最大的不同在于其不是多線程而是多進程。

    • 多線程由于可以共享堆內(nèi)存,因此可以簡單的采取內(nèi)存作為標記存儲位置。而進程之間甚至可能都不在同一臺物理機上,因此需要將標記存儲在一個所有進程都能看到的地方。


    什么是分布式鎖?


    • 當在分布式模型下,數(shù)據(jù)只有一份(或有限制),此時需要利用鎖的技術控制某一時刻修改數(shù)據(jù)的進程數(shù)。

    • 與單機模式下的鎖不僅需要保證進程可見,還需要考慮進程與鎖之間的網(wǎng)絡問題。(我覺得分布式情況下之所以問題變得復雜,主要就是需要考慮到網(wǎng)絡的延時和不可靠。。。一個大坑)

    • 分布式鎖還是可以將標記存在內(nèi)存,只是該內(nèi)存不是某個進程分配的內(nèi)存而是公共內(nèi)存如 Redis、Memcache。至于利用數(shù)據(jù)庫、文件等做鎖與單機的實現(xiàn)是一樣的,只要保證標記能互斥就行。

    我們需要怎樣的分布式鎖?


    • 可以保證在分布式部署的應用集群中,同一個方法在同一時間只能被一臺機器-上的一個線程執(zhí)行。

    • 這把鎖要是一把可重入鎖(避免死鎖)

    • 這把鎖最好是一把阻塞鎖(根據(jù)業(yè)務需求考慮要不要這條)

    • 這把鎖最好是一把公平鎖(根據(jù)業(yè)務需求考慮要不要這條)

    • 有高可用的獲取鎖和釋放鎖功能

    • 獲取鎖和釋放鎖的性能要好

    基于數(shù)據(jù)庫做分布式鎖


    基于樂觀鎖

    基于表主鍵唯一做分布式鎖

    思路:利用主鍵唯一的特性,如果有多個請求同時提交到數(shù)據(jù)庫的話,數(shù)據(jù)庫會保證只有一個操作可以成功,那么我們就可以認為操作成功的那個線程獲得了該方法的鎖,當方法執(zhí)行完畢之后,想要釋放鎖的話,刪除這條數(shù)據(jù)庫記錄即可。

    上面這種簡單的實現(xiàn)有以下幾個問題:

    • 這把鎖強依賴數(shù)據(jù)庫的可用性,數(shù)據(jù)庫是一個單點,一旦數(shù)據(jù)庫掛掉,會導致業(yè)務系統(tǒng)不可用。

    • 這把鎖沒有失效時間,一旦解鎖操作失敗,就會導致鎖記錄一直在數(shù)據(jù)庫中,其他線程無法再獲得到鎖。

    • 這把鎖只能是非阻塞的,因為數(shù)據(jù)的 insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的線程并不會進入排隊隊列,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作。

    • 這把鎖是非重入的,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數(shù)據(jù)中數(shù)據(jù)已經(jīng)存在了。

    • 這把鎖是非公平鎖,所有等待鎖的線程憑運氣去爭奪鎖。

    • 在 MySQL 數(shù)據(jù)庫中采用主鍵沖突防重,在大并發(fā)情況下有可能會造成鎖表現(xiàn)象。

    當然,我們也可以有其他方式解決上面的問題。
    • 數(shù)據(jù)庫是單點?搞兩個數(shù)據(jù)庫,數(shù)據(jù)之前雙向同步,一旦掛掉快速切換到備庫上。

    • 沒有失效時間?只要做一個定時任務,每隔一定時間把數(shù)據(jù)庫中的超時數(shù)據(jù)清理一遍。

    • 非阻塞的?搞一個 while 循環(huán),直到 insert 成功再返回成功。

    • 非重入的?在數(shù)據(jù)庫表中加個字段,記錄當前獲得鎖的機器的主機信息和線程信息,那么下次再獲取鎖的時候先查詢數(shù)據(jù)庫,如果當前機器的主機信息和線程信息在數(shù)據(jù)庫可以查到的話,直接把鎖分配給他就可以了。

    • 非公平的?再建一張中間表,將等待鎖的線程全記錄下來,并根據(jù)創(chuàng)建時間排序,只有最先創(chuàng)建的允許獲取鎖。

    • 比較好的辦法是在程序中生產(chǎn)主鍵進行防重。

    基于表字段版本號做分布式鎖

    這個策略源于 mysql 的 mvcc 機制,使用這個策略其實本身沒有什么問題,唯一的問題就是對數(shù)據(jù)表侵入較大,我們要為每個表設計一個版本號字段,然后寫一條判斷 sql 每次進行判斷,增加了數(shù)據(jù)庫操作的次數(shù),在高并發(fā)的要求下,對數(shù)據(jù)庫連接的開銷也是無法忍受的。

    基于悲觀鎖

    基于數(shù)據(jù)庫排他鎖做分布式鎖

    在查詢語句后面增加for update,數(shù)據(jù)庫會在查詢過程中給數(shù)據(jù)庫表增加排他鎖 (注意:InnoDB 引擎在加鎖的時候,只有通過索引進行檢索的時候才會使用行級鎖,否則會使用表級鎖。這里我們希望使用行級鎖,就要給要執(zhí)行的方法字段名添加索引,值得注意的是,這個索引一定要創(chuàng)建成唯一索引,否則會出現(xiàn)多個重載方法之間無法同時被訪問的問題。重載方法的話建議把參數(shù)類型也加上。)。當某條記錄被加上排他鎖之后,其他線程無法再在該行記錄上增加排他鎖。

    我們可以認為獲得排他鎖的線程即可獲得分布式鎖,當獲取到鎖之后,可以執(zhí)行方法的業(yè)務邏輯,執(zhí)行完方法之后,通過
    connection.commit()操作來釋放鎖。

    這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題。

    • 阻塞鎖??for update語句會在執(zhí)行成功后立即返回,在執(zhí)行失敗時一直處于阻塞狀態(tài),直到成功。

    • 鎖定之后服務宕機,無法釋放?使用這種方式,服務宕機之后數(shù)據(jù)庫會自己把鎖釋放掉。

    但是還是無法直接解決數(shù)據(jù)庫單點和可重入問題。

    這里還可能存在另外一個問題,雖然我們對方法字段名使用了唯一索引,并且顯示使用 for update 來使用行級鎖。但是,MySQL 會對查詢進行優(yōu)化,即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由 MySQL 通過判斷不同執(zhí)行計劃的代價來決定的,如果 MySQL 認為全表掃效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。如果發(fā)生這種情況就悲劇了。。。

    還有一個問題,就是我們要使用排他鎖來進行分布式鎖的 lock,那么一個排他鎖長時間不提交,就會占用數(shù)據(jù)庫連接。一旦類似的連接變得多了,就可能把數(shù)據(jù)庫連接池撐爆。

    優(yōu)缺點

    優(yōu)點:簡單,易于理解

    缺點:會有各種各樣的問題(操作數(shù)據(jù)庫需要一定的開銷,使用數(shù)據(jù)庫的行級鎖并不一定靠譜,性能不靠譜)

    基于 Redis 做分布式鎖


    基于 REDIS 的 SETNX()、EXPIRE() 方法做分布式鎖


    setnx()


    setnx 的含義就是 SET if Not Exists,其主要有兩個參數(shù) setnx(key, value)。該方法是原子的,如果 key 不存在,則設置當前 key 成功,返回 1;如果當前 key 已經(jīng)存在,則設置當前 key 失敗,返回 0。

    expire()


    expire 設置過期時間,要注意的是 setnx 命令不能設置 key 的超時時間,只能通過 expire() 來對 key 設置。

    使用步驟


    1、setnx(lockkey, 1) 如果返回 0,則說明占位失敗;如果返回 1,則說明占位成功

    2、expire() 命令對 lockkey 設置超時時間,為的是避免死鎖問題。

    3、執(zhí)行完業(yè)務代碼后,可以通過 delete 命令刪除 key。

    這個方案其實是可以解決日常工作中的需求的,但從技術方案的探討上來說,可能還有一些可以完善的地方。比如,如果在第一步 setnx 執(zhí)行成功后,在 expire() 命令執(zhí)行成功前,發(fā)生了宕機的現(xiàn)象,那么就依然會出現(xiàn)死鎖的問題,所以如果要對其進行完善的話,可以使用 redis 的 setnx()、get() 和 getset() 方法來實現(xiàn)分布式鎖。

    基于 REDIS 的 SETNX()、GET()、GETSET()方法做分布式鎖


    這個方案的背景主要是在 setnx() 和 expire() 的方案上針對可能存在的死鎖問題,做了一些優(yōu)化。

    getset()


    這個命令主要有兩個參數(shù) getset(key,newValue)。該方法是原子的,對 key 設置 newValue 這個值,并且返回 key 原來的舊值。假設 key 原來是不存在的,那么多次執(zhí)行這個命令,會出現(xiàn)下邊的效果:

    • getset(key, “value1”) 返回 null 此時 key 的值會被設置為 value1

    • getset(key, “value2”) 返回 value1 此時 key 的值會被設置為 value2

    • 依次類推!

    使用步驟


    • setnx(lockkey, 當前時間+過期超時時間),如果返回 1,則獲取鎖成功;如果返回 0 則沒有獲取到鎖,轉向 2。

    • get(lockkey) 獲取值 oldExpireTime ,并將這個 value 值與當前的系統(tǒng)時間進行比較,如果小于當前系統(tǒng)時間,則認為這個鎖已經(jīng)超時,可以允許別的請求重新獲取,轉向 3。

    • 計算 newExpireTime = 當前時間+過期超時時間,然后 getset(lockkey, newExpireTime) 會返回當前 lockkey 的值currentExpireTime。

    • 判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說明當前 getset 設置成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那么當前請求可以直接返回失敗,或者繼續(xù)重試。

    • 在獲取到鎖之后,當前線程可以開始自己的業(yè)務處理,當處理完畢后,比較自己的處理時間和對于鎖設置的超時時間,如果小于鎖設置的超時時間,則直接執(zhí)行 delete 釋放鎖;如果大于鎖設置的超時時間,則不需要再鎖進行處理。

    import?cn.com.tpig.cache.redis.RedisService;
    import?cn.com.tpig.utils.SpringUtils;

    //redis分布式鎖
    public?final?class?RedisLockUtil?{

    ????private?static?final?int?defaultExpire = 60;

    ????private?RedisLockUtil()?{
    ????????//
    ????}

    ????/**
    ?????* 加鎖
    ?????* @param?key redis key
    ?????* @param?expire 過期時間,單位秒
    ?????* @return?true:加鎖成功,false,加鎖失敗
    ?????*/

    ????public?static?boolean?lock(String key, int?expire)?{

    ????????RedisService redisService = SpringUtils.getBean(RedisService.class);
    ????????long?status = redisService.setnx(key, "1");

    ????????if(status == 1) {
    ????????????redisService.expire(key, expire);
    ????????????return?true;
    ????????}

    ????????return?false;
    ????}

    ????public?static?boolean?lock(String key)?{
    ????????return?lock2(key, defaultExpire);
    ????}

    ????/**
    ?????* 加鎖
    ?????* @param?key redis key
    ?????* @param?expire 過期時間,單位秒
    ?????* @return?true:加鎖成功,false,加鎖失敗
    ?????*/

    ????public?static?boolean?lock2(String key, int?expire)?{

    ????????RedisService redisService = SpringUtils.getBean(RedisService.class);

    ????????long?value = System.currentTimeMillis() + expire;
    ????????long?status = redisService.setnx(key, String.valueOf(value));

    ????????if(status == 1) {
    ????????????return?true;
    ????????}
    ????????long?oldExpireTime = Long.parseLong(redisService.get(key, "0"));
    ????????if(oldExpireTime < System.currentTimeMillis()) {
    ????????????//超時
    ????????????long?newExpireTime = System.currentTimeMillis() + expire;
    ????????????long?currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
    ????????????if(currentExpireTime == oldExpireTime) {
    ????????????????return?true;
    ????????????}
    ????????}
    ????????return?false;
    ????}

    ????public?static?void?unLock1(String key)?{
    ????????RedisService redisService = SpringUtils.getBean(RedisService.class);
    ????????redisService.del(key);
    ????}

    ????public?static?void?unLock2(String key)?{
    ????????RedisService redisService = SpringUtils.getBean(RedisService.class);
    ????????long?oldExpireTime = Long.parseLong(redisService.get(key, "0"));
    ????????if(oldExpireTime > System.currentTimeMillis()) {
    ????????????redisService.del(key);
    ????????}
    ???}
    }


    public?void?drawRedPacket(long?userId) {
    ????String key = "draw.redpacket.userid:"?+ userId;

    ????boolean lock?= RedisLockUtil.lock2(key, 60);
    ????if(lock) {
    ????????try?{
    ????????????//領取操作
    ????????} finally?{
    ????????????//釋放鎖
    ????????????RedisLockUtil.unLock(key);
    ????????}
    ????} else?{
    ????????new?RuntimeException("重復領取獎勵");
    ????}
    }

    基于 REDLOCK 做分布式鎖


    Redlock 是 Redis 的作者 antirez 給出的集群模式的 Redis 分布式鎖,它基于 N 個完全獨立的 Redis 節(jié)點(通常情況下 N 可以設置成 5)。

    算法的步驟如下:

    • 1、客戶端獲取當前時間,以毫秒為單位。

    • 2、客戶端嘗試獲取 N 個節(jié)點的鎖,(每個節(jié)點獲取鎖的方式和前面說的緩存鎖一樣),N 個節(jié)點以相同的 key 和 value 獲取鎖??蛻舳诵枰O置接口訪問超時,接口超時時間需要遠遠小于鎖超時時間,比如鎖自動釋放的時間是 10s,那么接口超時大概設置 5-50ms。這樣可以在有 redis 節(jié)點宕機后,訪問該節(jié)點時能盡快超時,而減小鎖的正常使用。

    • 3、客戶端計算在獲得鎖的時候花費了多少時間,方法是用當前時間減去在步驟一獲取的時間,只有客戶端獲得了超過 3 個節(jié)點的鎖,而且獲取鎖的時間小于鎖的超時時間,客戶端才獲得了分布式鎖。

    • 4、客戶端獲取的鎖的時間為設置的鎖超時時間減去步驟三計算出的獲取鎖花費時間。

    • 5、如果客戶端獲取鎖失敗了,客戶端會依次刪除所有的鎖。

    使用 Redlock 算法,可以保證在掛掉最多 2 個節(jié)點的時候,分布式鎖服務仍然能工作,這相比之前的數(shù)據(jù)庫鎖和緩存鎖大大提高了可用性,由于 redis 的高效性能,分布式緩存鎖性能并不比數(shù)據(jù)庫鎖差。

    但是,有一位分布式的專家寫了一篇文章《How to do distributed locking》,質(zhì)疑 Redlock 的正確性。

    https://mp.weixin.qq.com/s/1bPLk_VZhZ0QYNZS8LkviA

    https://blog.csdn.net/jek123456/article/details/72954106

    優(yōu)缺點

    優(yōu)點:?性能高

    缺點:

    失效時間設置多長時間為好?如何設置的失效時間太短,方法沒等執(zhí)行完,鎖就自動釋放了,那么就會產(chǎn)生并發(fā)問題。如果設置的時間太長,其他獲取鎖的線程就可能要平白的多等一段時間。

    基于 REDISSON 做分布式鎖

    redisson 是 redis 官方的分布式鎖組件。GitHub 地址:https://github.com/redisson/redisson

    上面的這個問題 ——> 失效時間設置多長時間為好?這個問題在 redisson 的做法是:每獲得一個鎖時,只設置一個很短的超時時間,同時起一個線程在每次快要到超時時間時去刷新鎖的超時時間。在釋放鎖的同時結束這個線程。

    基于 ZooKeeper 做分布式鎖


    ZOOKEEPER 鎖相關基礎知識

    • zk 一般由多個節(jié)點構成(單數(shù)),采用 zab 一致性協(xié)議。因此可以將 zk 看成一個單點結構,對其修改數(shù)據(jù)其內(nèi)部自動將所有節(jié)點數(shù)據(jù)進行修改而后才提供查詢服務。

    • zk 的數(shù)據(jù)以目錄樹的形式,每個目錄稱為 znode, znode 中可存儲數(shù)據(jù)(一般不超過 1M),還可以在其中增加子節(jié)點。

    • 子節(jié)點有三種類型。序列化節(jié)點,每在該節(jié)點下增加一個節(jié)點自動給該節(jié)點的名稱上自增。臨時節(jié)點,一旦創(chuàng)建這個 znode 的客戶端與服務器失去聯(lián)系,這個 znode 也將自動刪除。最后就是普通節(jié)點。

    • Watch 機制,client 可以監(jiān)控每個節(jié)點的變化,當產(chǎn)生變化會給 client 產(chǎn)生一個事件。

    ZK 基本鎖

    • 原理:利用臨時節(jié)點與 watch 機制。每個鎖占用一個普通節(jié)點 /lock,當需要獲取鎖時在 /lock 目錄下創(chuàng)建一個臨時節(jié)點,創(chuàng)建成功則表示獲取鎖成功,失敗則 watch/lock 節(jié)點,有刪除操作后再去爭鎖。臨時節(jié)點好處在于當進程掛掉后能自動上鎖的節(jié)點自動刪除即取消鎖。

    • 缺點:所有取鎖失敗的進程都監(jiān)聽父節(jié)點,很容易發(fā)生羊群效應,即當釋放鎖后所有等待進程一起來創(chuàng)建節(jié)點,并發(fā)量很大。

    ZK 鎖優(yōu)化

    • 原理:上鎖改為創(chuàng)建臨時有序節(jié)點,每個上鎖的節(jié)點均能創(chuàng)建節(jié)點成功,只是其序號不同。只有序號最小的可以擁有鎖,如果這個節(jié)點序號不是最小的則 watch 序號比本身小的前一個節(jié)點 (公平鎖)。

    步驟:

    • 1.在 /lock 節(jié)點下創(chuàng)建一個有序臨時節(jié)點 (EPHEMERAL_SEQUENTIAL)。

    • 2.判斷創(chuàng)建的節(jié)點序號是否最小,如果是最小則獲取鎖成功。不是則取鎖失敗,然后 watch 序號比本身小的前一個節(jié)點。

    • 3.當取鎖失敗,設置 watch 后則等待 watch 事件到來后,再次判斷是否序號最小。

    • 4.取鎖成功則執(zhí)行代碼,最后釋放鎖(刪除該節(jié)點)。

    import?java.io.IOException;
    import?java.util.ArrayList;
    import?java.util.Collections;
    import?java.util.List;
    import?java.util.concurrent.CountDownLatch;
    import?java.util.concurrent.TimeUnit;
    import?java.util.concurrent.locks.Condition;
    import?java.util.concurrent.locks.Lock;

    import?org.apache.zookeeper.CreateMode;
    import?org.apache.zookeeper.KeeperException;
    import?org.apache.zookeeper.WatchedEvent;
    import?org.apache.zookeeper.Watcher;
    import?org.apache.zookeeper.ZooDefs;
    import?org.apache.zookeeper.ZooKeeper;
    import?org.apache.zookeeper.data.Stat;

    public?class?DistributedLock?implements?Lock, Watcher{
    ????private?ZooKeeper zk;
    ????private?String root = "/locks";//根
    ????private?String lockName;//競爭資源的標志
    ????private?String waitNode;//等待前一個鎖
    ????private?String myZnode;//當前鎖
    ????private?CountDownLatch latch;//計數(shù)器
    ????private?int?sessionTimeout = 30000;
    ????private?List exception = new?ArrayList();

    ????/**
    ?????* 創(chuàng)建分布式鎖,使用前請確認config配置的zookeeper服務可用
    ?????* @param?config 127.0.0.1:2181
    ?????* @param?lockName 競爭資源標志,lockName中不能包含單詞lock
    ?????*/

    ????public?DistributedLock(String config, String lockName){
    ????????this.lockName = lockName;
    ????????// 創(chuàng)建一個與服務器的連接
    ????????try?{
    ????????????zk = new?ZooKeeper(config, sessionTimeout, this);
    ????????????Stat stat = zk.exists(root, false);
    ????????????if(stat == null){
    ????????????????// 創(chuàng)建根節(jié)點
    ????????????????zk.create(root, new?byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
    ????????????}
    ????????} catch?(IOException e) {
    ????????????exception.add(e);
    ????????} catch?(KeeperException e) {
    ????????????exception.add(e);
    ????????} catch?(InterruptedException e) {
    ????????????exception.add(e);
    ????????}
    ????}

    ????/**
    ?????* zookeeper節(jié)點的監(jiān)視器
    ?????*/

    ????public?void?process(WatchedEvent event)?{
    ????????if(this.latch != null) {
    ????????????this.latch.countDown();
    ????????}
    ????}

    ????public?void?lock()?{
    ????????if(exception.size() > 0){
    ????????????throw?new?LockException(exception.get(0));
    ????????}
    ????????try?{
    ????????????if(this.tryLock()){
    ????????????????System.out.println("Thread "?+ Thread.currentThread().getId() + " "?+myZnode + " get lock true");
    ????????????????return;
    ????????????}
    ????????????else{
    ????????????????waitForLock(waitNode, sessionTimeout);//等待鎖
    ????????????}
    ????????} catch?(KeeperException e) {
    ????????????throw?new?LockException(e);
    ????????} catch?(InterruptedException e) {
    ????????????throw?new?LockException(e);
    ????????}
    ????}

    ????public?boolean?tryLock()?{
    ????????try?{
    ????????????String splitStr = "_lock_";
    ????????????if(lockName.contains(splitStr))
    ????????????????throw?new?LockException("lockName can not contains \\u000B");
    ????????????//創(chuàng)建臨時子節(jié)點
    ????????????myZnode = zk.create(root + "/"?+ lockName + splitStr, new?byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
    ????????????System.out.println(myZnode + " is created ");
    ????????????//取出所有子節(jié)點
    ????????????List subNodes = zk.getChildren(root, false);
    ????????????//取出所有l(wèi)ockName的鎖
    ????????????List lockObjNodes = new?ArrayList();
    ????????????for?(String node : subNodes) {
    ????????????????String _node = node.split(splitStr)[0];
    ????????????????if(_node.equals(lockName)){
    ????????????????????lockObjNodes.add(node);
    ????????????????}
    ????????????}
    ????????????Collections.sort(lockObjNodes);
    ????????????System.out.println(myZnode + "=="?+ lockObjNodes.get(0));
    ????????????if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
    ????????????????//如果是最小的節(jié)點,則表示取得鎖
    ????????????????return?true;
    ????????????}
    ????????????//如果不是最小的節(jié)點,找到比自己小1的節(jié)點
    ????????????String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
    ????????????waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
    ????????} catch?(KeeperException e) {
    ????????????throw?new?LockException(e);
    ????????} catch?(InterruptedException e) {
    ????????????throw?new?LockException(e);
    ????????}
    ????????return?false;
    ????}

    ????public?boolean?tryLock(long?time, TimeUnit unit)?{
    ????????try?{
    ????????????if(this.tryLock()){
    ????????????????return?true;
    ????????????}
    ????????????return?waitForLock(waitNode,time);
    ????????} catch?(Exception e) {
    ????????????e.printStackTrace();
    ????????}
    ????????return?false;
    ????}

    ????private?boolean?waitForLock(String lower, long?waitTime)?throws?InterruptedException, KeeperException {
    ????????Stat stat = zk.exists(root + "/"?+ lower,true);
    ????????//判斷比自己小一個數(shù)的節(jié)點是否存在,如果不存在則無需等待鎖,同時注冊監(jiān)聽
    ????????if(stat != null){
    ????????????System.out.println("Thread "?+ Thread.currentThread().getId() + " waiting for "?+ root + "/"?+ lower);
    ????????????this.latch = new?CountDownLatch(1);
    ????????????this.latch.await(waitTime, TimeUnit.MILLISECONDS);
    ????????????this.latch = null;
    ????????}
    ????????return?true;
    ????}

    ????public?void?unlock()?{
    ????????try?{
    ????????????System.out.println("unlock "?+ myZnode);
    ????????????zk.delete(myZnode,-1);
    ????????????myZnode = null;
    ????????????zk.close();
    ????????} catch?(InterruptedException e) {
    ????????????e.printStackTrace();
    ????????} catch?(KeeperException e) {
    ????????????e.printStackTrace();
    ????????}
    ????}

    ????public?void?lockInterruptibly()?throws?InterruptedException {
    ????????this.lock();
    ????}

    ????public?Condition newCondition()?{
    ????????return?null;
    ????}

    ????public?class?LockException?extends?RuntimeException?{
    ????????private?static?final?long?serialVersionUID = 1L;
    ????????public?LockException(String e){
    ????????????super(e);
    ????????}
    ????????public?LockException(Exception e){
    ????????????super(e);
    ????????}
    ????}
    }
    優(yōu)缺點

    優(yōu)點:

    有效的解決單點問題,不可重入問題,非阻塞問題以及鎖無法釋放的問題。實現(xiàn)起來較為簡單。

    缺點:

    性能上可能并沒有緩存服務那么高,因為每次在創(chuàng)建鎖和釋放鎖的過程中,都要動態(tài)創(chuàng)建、銷毀臨時節(jié)點來實現(xiàn)鎖功能。ZK 中創(chuàng)建和刪除節(jié)點只能通過 Leader 服務器來執(zhí)行,然后將數(shù)據(jù)同步到所有的 Follower 機器上。還需要對 ZK的原理有所了解。

    基于 Consul 做分布式鎖


    DD 寫過類似文章,其實主要利用 Consul 的 Key / Value 存儲 API 中的 acquire 和 release 操作來實現(xiàn)。

    文章地址:http://blog.didispace.com/spring-cloud-consul-lock-and-semphore/

    使用分布式鎖的注意事項

    1、注意分布式鎖的開銷

    2、注意加鎖的粒度

    3、加鎖的方式

    總結


    無論你身處一個什么樣的公司,最開始的工作可能都需要從最簡單的做起。不要提阿里和騰訊的業(yè)務場景 qps 如何大,因為在這樣的大場景中你未必能親自參與項目,親自參與項目未必能是核心的設計者,是核心的設計者未必能獨自設計。希望大家能根據(jù)自己公司業(yè)務場景,選擇適合自己項目的方案。



    粉絲福利:108本java從入門到大神精選電子書領取

    ???

    ?長按上方鋒哥微信二維碼?2 秒
    備注「1234」即可獲取資料以及
    可以進入java1234官方微信群



    感謝點贊支持下哈?

    瀏覽 57
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    69黄色片 | 一区二区三区免费无码 | 极品3p肏屄 | 亚洲日韩国产精品一区无码AV | 欧美午夜成人 |