<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          分布式鎖的三種實(shí)現(xiàn)方式

          共 2217字,需瀏覽 5分鐘

           ·

          2021-01-06 15:05

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

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

          ? 作者?|? BarryW

          來(lái)源 |? urlify.cn/JrAZBb

          76套java從入門到精通實(shí)戰(zhàn)課程分享

          分布式鎖三種實(shí)現(xiàn)方式:

          1

          2

          3

          1. 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖;

          2. 基于緩存(Redis等)實(shí)現(xiàn)分布式鎖;

          3. 基于Zookeeper實(shí)現(xiàn)分布式鎖;

          ?

          一, 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖

          1. 悲觀鎖

          利用select … where … for update 排他鎖

          注意: 其他附加功能與實(shí)現(xiàn)一基本一致,這里需要注意的是“where name=lock ”,name字段必須要走索引,否則會(huì)鎖表。有些情況下,比如表不大,mysql優(yōu)化器會(huì)不走這個(gè)索引,導(dǎo)致鎖表問(wèn)題。

          ?

          2. 樂(lè)觀鎖

          所謂樂(lè)觀鎖與前邊最大區(qū)別在于基于CAS思想,是不具有互斥性,不會(huì)產(chǎn)生鎖等待而消耗資源,操作過(guò)程中認(rèn)為不存在并發(fā)沖突,只有update version失敗后才能覺(jué)察到。我們的搶購(gòu)、秒殺就是用了這種實(shí)現(xiàn)以防止超賣。
          通過(guò)增加遞增的版本號(hào)字段實(shí)現(xiàn)樂(lè)觀鎖

          ?

          二, 基于緩存(Redis等)實(shí)現(xiàn)分布式鎖

          1. 使用命令介紹:
          (1)SETNX
          SETNX key val:當(dāng)且僅當(dāng)key不存在時(shí),set一個(gè)key為val的字符串,返回1;若key存在,則什么都不做,返回0。
          (2)expire
          expire key timeout:為key設(shè)置一個(gè)超時(shí)時(shí)間,單位為second,超過(guò)這個(gè)時(shí)間鎖會(huì)自動(dòng)釋放,避免死鎖。
          (3)delete
          delete key:刪除key

          在使用Redis實(shí)現(xiàn)分布式鎖的時(shí)候,主要就會(huì)使用到這三個(gè)命令。

          ?

          2. 實(shí)現(xiàn)思想:
          (1)獲取鎖的時(shí)候,使用setnx加鎖,并使用expire命令為鎖添加一個(gè)超時(shí)時(shí)間,超過(guò)該時(shí)間則自動(dòng)釋放鎖,鎖的value值為一個(gè)隨機(jī)生成的UUID,通過(guò)此在釋放鎖的時(shí)候進(jìn)行判斷。
          (2)獲取鎖的時(shí)候還設(shè)置一個(gè)獲取的超時(shí)時(shí)間,若超過(guò)這個(gè)時(shí)間則放棄獲取鎖。
          (3)釋放鎖的時(shí)候,通過(guò)UUID判斷是不是該鎖,若是該鎖,則執(zhí)行delete進(jìn)行鎖釋放。

          ?

          3. 分布式鎖的簡(jiǎn)單實(shí)現(xiàn)代碼:

          1?/**
          ??2??*?分布式鎖的簡(jiǎn)單實(shí)現(xiàn)代碼??4??*/
          ??5?public?class?DistributedLock?{
          ??6?
          ??7?????private?final?JedisPool?jedisPool;
          ??8?
          ??9?????public?DistributedLock(JedisPool?jedisPool)?{
          ?10?????????this.jedisPool?=?jedisPool;
          ?11?????}
          ?12?
          ?13?????/**
          ?14??????*?加鎖
          ?15??????*?@param?lockName???????鎖的key
          ?16??????*?@param?acquireTimeout?獲取超時(shí)時(shí)間
          ?17??????*?@param?timeout????????鎖的超時(shí)時(shí)間
          ?18??????*?@return?鎖標(biāo)識(shí)
          ?19??????*/
          ?20?????public?String?lockWithTimeout(String?lockName,?long?acquireTimeout,?long?timeout)?{
          ?21?????????Jedis?conn?=?null;
          ?22?????????String?retIdentifier?=?null;
          ?23?????????try?{
          ?24?????????????//?獲取連接
          ?25?????????????conn?=?jedisPool.getResource();
          ?26?????????????//?隨機(jī)生成一個(gè)value
          ?27?????????????String?identifier?=?UUID.randomUUID().toString();
          ?28?????????????//?鎖名,即key值
          ?29?????????????String?lockKey?=?"lock:"?+?lockName;
          ?30?????????????//?超時(shí)時(shí)間,上鎖后超過(guò)此時(shí)間則自動(dòng)釋放鎖
          ?31?????????????int?lockExpire?=?(int)?(timeout?/?1000);
          ?32?
          ?33?????????????//?獲取鎖的超時(shí)時(shí)間,超過(guò)這個(gè)時(shí)間則放棄獲取鎖
          ?34?????????????long?end?=?System.currentTimeMillis()?+?acquireTimeout;
          ?35?????????????while?(System.currentTimeMillis()??36?????????????????if?(conn.setnx(lockKey,?identifier)?==?1)?{
          ?37?????????????????????conn.expire(lockKey,?lockExpire);
          ?38?????????????????????//?返回value值,用于釋放鎖時(shí)間確認(rèn)
          ?39?????????????????????retIdentifier?=?identifier;
          ?40?????????????????????return?retIdentifier;
          ?41?????????????????}
          ?42?????????????????//?返回-1代表key沒(méi)有設(shè)置超時(shí)時(shí)間,為key設(shè)置一個(gè)超時(shí)時(shí)間
          ?43?????????????????if?(conn.ttl(lockKey)?==?-1)?{
          ?44?????????????????????conn.expire(lockKey,?lockExpire);
          ?45?????????????????}
          ?46?
          ?47?????????????????try?{
          ?48?????????????????????Thread.sleep(10);
          ?49?????????????????}?catch?(InterruptedException?e)?{
          ?50?????????????????????Thread.currentThread().interrupt();
          ?51?????????????????}
          ?52?????????????}
          ?53?????????}?catch?(JedisException?e)?{
          ?54?????????????e.printStackTrace();
          ?55?????????}?finally?{
          ?56?????????????if?(conn?!=?null)?{
          ?57?????????????????conn.close();
          ?58?????????????}
          ?59?????????}
          ?60?????????return?retIdentifier;
          ?61?????}
          ?62?
          ?63?????/**
          ?64??????*?釋放鎖
          ?65??????*?@param?lockName???鎖的key
          ?66??????*?@param?identifier?釋放鎖的標(biāo)識(shí)
          ?67??????*?@return
          ?68??????*/
          ?69?????public?boolean?releaseLock(String?lockName,?String?identifier)?{
          ?70?????????Jedis?conn?=?null;
          ?71?????????String?lockKey?=?"lock:"?+?lockName;
          ?72?????????boolean?retFlag?=?false;
          ?73?????????try?{
          ?74?????????????conn?=?jedisPool.getResource();
          ?75?????????????while?(true)?{
          ?76?????????????????//?監(jiān)視lock,準(zhǔn)備開(kāi)始事務(wù)
          ?77?????????????????conn.watch(lockKey);
          ?78?????????????????//?通過(guò)前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖
          ?79?????????????????if?(identifier.equals(conn.get(lockKey)))?{
          ?80?????????????????????Transaction?transaction?=?conn.multi();
          ?81?????????????????????transaction.del(lockKey);
          ?82?????????????????????List?results?=?transaction.exec();
          ?83?????????????????????if?(results?==?null)?{
          ?84?????????????????????????continue;
          ?85?????????????????????}
          ?86?????????????????????retFlag?=?true;
          ?87?????????????????}
          ?88?????????????????conn.unwatch();
          ?89?????????????????break;
          ?90?????????????}
          ?91?????????}?catch?(JedisException?e)?{
          ?92?????????????e.printStackTrace();
          ?93?????????}?finally?{
          ?94?????????????if?(conn?!=?null)?{
          ?95?????????????????conn.close();
          ?96?????????????}
          ?97?????????}
          ?98?????????return?retFlag;
          ?99?????}
          100?}

          ?

          4.?測(cè)試剛才實(shí)現(xiàn)的分布式鎖

          例子中使用50個(gè)線程模擬秒殺一個(gè)商品,使用–運(yùn)算符來(lái)實(shí)現(xiàn)商品減少,從結(jié)果有序性就可以看出是否為加鎖狀態(tài)。

          模擬秒殺服務(wù),在其中配置了jedis線程池,在初始化的時(shí)候傳給分布式鎖,供其使用。

          public?class?Service?{

          ????private?static?JedisPool?pool?=?null;

          ????private?DistributedLock?lock?=?new?DistributedLock(pool);

          ????int?n?=?500;

          ????static?{
          ????????JedisPoolConfig?config?=?new?JedisPoolConfig();
          ????????//?設(shè)置最大連接數(shù)
          ????????config.setMaxTotal(200);
          ????????//?設(shè)置最大空閑數(shù)
          ????????config.setMaxIdle(8);
          ????????//?設(shè)置最大等待時(shí)間
          ????????config.setMaxWaitMillis(1000?*?100);
          ????????//?在borrow一個(gè)jedis實(shí)例時(shí),是否需要驗(yàn)證,若為true,則所有jedis實(shí)例均是可用的
          ????????config.setTestOnBorrow(true);
          ????????pool?=?new?JedisPool(config,?"127.0.0.1",?6379,?3000);
          ????}

          ????public?void?seckill()?{
          ????????//?返回鎖的value值,供釋放鎖時(shí)候進(jìn)行判斷
          ????????String?identifier?=?lock.lockWithTimeout("resource",?5000,?1000);
          ????????System.out.println(Thread.currentThread().getName()?+?"獲得了鎖");
          ????????System.out.println(--n);
          ????????lock.releaseLock("resource",?identifier);
          ????}
          }

          ?

          模擬線程進(jìn)行秒殺服務(wù);

          public?class?ThreadA?extends?Thread?{
          ????private?Service?service;

          ????public?ThreadA(Service?service)?{
          ????????this.service?=?service;
          ????}

          ????@Override
          ????public?void?run()?{
          ????????service.seckill();
          ????}
          }

          public?class?Test?{
          ????public?static?void?main(String[]?args)?{
          ????????Service?service?=?new?Service();
          ????????for?(int?i?=?0;?i?????????????ThreadA?threadA?=?new?ThreadA(service);
          ????????????threadA.start();
          ????????}
          ????}
          }

          結(jié)果如下,結(jié)果為有序的:

          若注釋掉使用鎖的部分:

          public?void?seckill()?{
          ????//?返回鎖的value值,供釋放鎖時(shí)候進(jìn)行判斷
          ????//String?indentifier?=?lock.lockWithTimeout("resource",?5000,?1000);
          ????System.out.println(Thread.currentThread().getName()?+?"獲得了鎖");
          ????System.out.println(--n);
          ????//lock.releaseLock("resource",?indentifier);
          }

          從結(jié)果可以看出,有一些是異步進(jìn)行的:

          ?

          三, 基于Zookeeper實(shí)現(xiàn)分布式鎖?

          ZooKeeper是一個(gè)為分布式應(yīng)用提供一致性服務(wù)的開(kāi)源組件,它內(nèi)部是一個(gè)分層的文件系統(tǒng)目錄樹(shù)結(jié)構(gòu),規(guī)定同一個(gè)目錄下只能有一個(gè)唯一文件名。基于ZooKeeper實(shí)現(xiàn)分布式鎖的步驟如下:

          (1)創(chuàng)建一個(gè)目錄mylock;
          (2)線程A想獲取鎖就在mylock目錄下創(chuàng)建臨時(shí)順序節(jié)點(diǎn);
          (3)獲取mylock目錄下所有的子節(jié)點(diǎn),然后獲取比自己小的兄弟節(jié)點(diǎn),如果不存在,則說(shuō)明當(dāng)前線程順序號(hào)最小,獲得鎖;
          (4)線程B獲取所有節(jié)點(diǎn),判斷自己不是最小節(jié)點(diǎn),設(shè)置監(jiān)聽(tīng)比自己次小的節(jié)點(diǎn);
          (5)線程A處理完,刪除自己的節(jié)點(diǎn),線程B監(jiān)聽(tīng)到變更事件,判斷自己是不是最小的節(jié)點(diǎn),如果是則獲得鎖。

          這里推薦一個(gè)Apache的開(kāi)源庫(kù)Curator,它是一個(gè)ZooKeeper客戶端,Curator提供的InterProcessMutex是分布式鎖的實(shí)現(xiàn),acquire方法用于獲取鎖,release方法用于釋放鎖。

          實(shí)現(xiàn)源碼如下:

          import?lombok.extern.slf4j.Slf4j;
          import?org.apache.commons.lang.StringUtils;
          import?org.apache.curator.framework.CuratorFramework;
          import?org.apache.curator.framework.CuratorFrameworkFactory;
          import?org.apache.curator.retry.RetryNTimes;
          import?org.apache.zookeeper.CreateMode;
          import?org.apache.zookeeper.data.Stat;
          import?org.springframework.beans.factory.annotation.Value;
          import?org.springframework.context.annotation.Bean;
          import?org.springframework.stereotype.Component;

          /**
          ?*?分布式鎖Zookeeper實(shí)現(xiàn)
          ?*
          ?*/
          @Slf4j
          @Component
          public?class?ZkLock?implements?DistributionLock?{
          private?String?zkAddress?=?"zk_adress";
          ????private?static?final?String?root?=?"package?root";
          ????private?CuratorFramework?zkClient;

          ????private?final?String?LOCK_PREFIX?=?"/lock_";

          ????@Bean
          ????public?DistributionLock?initZkLock()?{
          ????????if?(StringUtils.isBlank(root))?{
          ????????????throw?new?RuntimeException("zookeeper?'root'?can't?be?null");
          ????????}
          ????????zkClient?=?CuratorFrameworkFactory
          ????????????????.builder()
          ????????????????.connectString(zkAddress)
          ????????????????.retryPolicy(new?RetryNTimes(2000,?20000))
          ????????????????.namespace(root)
          ????????????????.build();
          ????????zkClient.start();
          ????????return?this;
          ????}

          ????public?boolean?tryLock(String?lockName)?{
          ????????lockName?=?LOCK_PREFIX+lockName;
          ????????boolean?locked?=?true;
          ????????try?{
          ????????????Stat?stat?=?zkClient.checkExists().forPath(lockName);
          ????????????if?(stat?==?null)?{
          ????????????????log.info("tryLock:{}",?lockName);
          ????????????????stat?=?zkClient.checkExists().forPath(lockName);
          ????????????????if?(stat?==?null)?{
          ????????????????????zkClient
          ????????????????????????????.create()
          ????????????????????????????.creatingParentsIfNeeded()
          ????????????????????????????.withMode(CreateMode.EPHEMERAL)
          ????????????????????????????.forPath(lockName,?"1".getBytes());
          ????????????????}?else?{
          ????????????????????log.warn("double-check?stat.version:{}",?stat.getAversion());
          ????????????????????locked?=?false;
          ????????????????}
          ????????????}?else?{
          ????????????????log.warn("check?stat.version:{}",?stat.getAversion());
          ????????????????locked?=?false;
          ????????????}
          ????????}?catch?(Exception?e)?{
          ????????????locked?=?false;
          ????????}
          ????????return?locked;
          ????}

          ????public?boolean?tryLock(String?key,?long?timeout)?{
          ????????return?false;
          ????}

          ????public?void?release(String?lockName)?{
          ????????lockName?=?LOCK_PREFIX+lockName;
          ????????try?{
          ????????????zkClient
          ????????????????????.delete()
          ????????????????????.guaranteed()
          ????????????????????.deletingChildrenIfNeeded()
          ????????????????????.forPath(lockName);
          ????????????log.info("release:{}",?lockName);
          ????????}?catch?(Exception?e)?{
          ????????????log.error("刪除",?e);
          ????????}
          ????}

          ????public?void?setZkAddress(String?zkAddress)?{
          ????????this.zkAddress?=?zkAddress;
          ????}
          }

          優(yōu)點(diǎn):具備高可用、可重入、阻塞鎖特性,可解決失效死鎖問(wèn)題。

          缺點(diǎn):因?yàn)樾枰l繁的創(chuàng)建和刪除節(jié)點(diǎn),性能上不如Redis方式。

          ?

          四,對(duì)比


          數(shù)據(jù)庫(kù)分布式鎖實(shí)現(xiàn)
          缺點(diǎn):

          1.db操作性能較差,并且有鎖表的風(fēng)險(xiǎn)
          2.非阻塞操作失敗后,需要輪詢,占用cpu資源;
          3.長(zhǎng)時(shí)間不commit或者長(zhǎng)時(shí)間輪詢,可能會(huì)占用較多連接資源

          Redis(緩存)分布式鎖實(shí)現(xiàn)
          缺點(diǎn):

          1.鎖刪除失敗 過(guò)期時(shí)間不好控制
          2.非阻塞,操作失敗后,需要輪詢,占用cpu資源;

          ZK分布式鎖實(shí)現(xiàn)
          缺點(diǎn):性能不如redis實(shí)現(xiàn),主要原因是寫操作(獲取鎖釋放鎖)都需要在Leader上執(zhí)行,然后同步到follower。

          總之:ZooKeeper有較好的性能和可靠性。

          ?

          從理解的難易程度角度(從低到高)數(shù)據(jù)庫(kù) > 緩存 > Zookeeper

          從實(shí)現(xiàn)的復(fù)雜性角度(從低到高)Zookeeper >= 緩存 > 數(shù)據(jù)庫(kù)

          從性能角度(從高到低)緩存 > Zookeeper >= 數(shù)據(jù)庫(kù)

          從可靠性角度(從高到低)Zookeeper > 緩存 > 數(shù)據(jù)庫(kù)




          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ???

          ?長(zhǎng)按上方微信二維碼?2 秒


          感謝點(diǎn)贊支持下哈?

          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    国产性爱第一页 | 精品免费囯产一区二区三区四区视频 | 韩国av中文字幕 黄片网站在线播放 | 久操视频免费在线观看 | 性XXXX丰满孕妇XXXX另类 |