<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ù)據(jù)庫、redis和zookeeper實現(xiàn)的分布式鎖

          共 4917字,需瀏覽 10分鐘

           ·

          2021-01-06 15:06

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

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

          ? 作者?|??曹自標

          來源 |? urlify.cn/aMJJrm

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

          基于數(shù)據(jù)庫

          基于數(shù)據(jù)庫(MySQL)的方案,一般分為3類:基于表記錄、樂觀鎖和悲觀鎖

          基于表記錄

          用表主鍵或表字段加唯一性索引便可實現(xiàn),如下;

          CREATE?TABLE?`database_lock`?(
          ?`id`?BIGINT?NOT?NULL?AUTO_INCREMENT,
          ?`resource`?int?NOT?NULL?COMMENT?'鎖定的資源',
          ?`description`?varchar(1024)?NOT?NULL?DEFAULT?""?COMMENT?'描述',
          ?PRIMARY?KEY?(`id`),
          ?UNIQUE?KEY?`uiq_idx_resource`?(`resource`)?
          )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4?COMMENT='數(shù)據(jù)庫分布式鎖表';

          想獲得鎖插入一條數(shù)據(jù)

          INSERT?INTO?database_lock(resource,?description)?VALUES?(1,?'lock');

          解鎖刪除數(shù)據(jù):

          DELETE?FROM?database_lock?WHERE?resource=1;

          這種實現(xiàn)方式非常的簡單,但是需要注意以下幾點:

          • 這種鎖沒有失效時間,一旦釋放鎖的操作失敗就會導致鎖記錄一直在數(shù)據(jù)庫中,其它線程無法獲得鎖。這個缺陷也很好解決,比如可以做一個定時任務去定時清理。

          • 這種鎖的可靠性依賴于數(shù)據(jù)庫。建議設置備庫,避免單點,進一步提高可靠性。

          • 這種鎖是非阻塞的,因為插入數(shù)據(jù)失敗之后會直接報錯,想要獲得鎖就需要再次操作。如果需要阻塞式的,可以弄個for循環(huán)、while循環(huán)之類的,直至INSERT成功再返回。

          • 這種鎖也是非可重入的,因為同一個線程在沒有釋放鎖之前無法再次獲得鎖,因為數(shù)據(jù)庫中已經(jīng)存在同一份記錄了。想要實現(xiàn)可重入鎖,可以在數(shù)據(jù)庫中添加一些字段,比如獲得鎖的主機信息、線程信息等,那么在再次獲得鎖的時候可以先查詢數(shù)據(jù),如果當前的主機信息和線程信息等能被查到的話,可以直接把鎖分配給它。

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

          基于樂觀鎖

          可基于MVCC機制實現(xiàn)

          • 優(yōu)點:在檢測數(shù)據(jù)沖突時并不依賴數(shù)據(jù)庫本身的鎖機制,不會影響請求的性能,當產(chǎn)生并發(fā)且并發(fā)量較小的時候只有少部分請求會失敗

          • 缺點:唯一癿問題就是對數(shù)據(jù)表侵入較大,我們
            要為每個表設計一個版本號字段,然后寫一條判斷 sql 每次進行判斷,增加了數(shù)據(jù)庫操作的次數(shù),在高并發(fā)要求下,對數(shù)據(jù)庫連接的開銷也是無法忍受的。

          基于悲觀鎖

          在查詢語句后面增加for update, 數(shù)據(jù)庫會在查詢過程中給數(shù)據(jù)庫表增加排他鎖, 當某條記錄被加上排他鎖之后,其他線程無法再在該行記錄上增加排他鎖。

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

          注意:在加鎖的時候,只有明確地指定主鍵(或索引)的才會執(zhí)行行鎖,否則MySQL 將會執(zhí)行表鎖

          加鎖前注意取消自動提交

          優(yōu)點:

          • 簡單易于理解

          • 嚴格保證數(shù)據(jù)訪問的安全

          缺點:

          • MySQL會對查詢進行優(yōu)化,如果任務全表掃描效率更高,便使用表鎖,導致性能問題

          • 如果一個排他鎖長時間不提交,就會占用數(shù)據(jù)庫連接,類似連接變多,就可能把連接池撐爆

          • 悲觀鎖使用不當還可能產(chǎn)生死鎖的情況

          • 每次請求都會額外產(chǎn)生加鎖的開銷且未獲取到鎖的請求將會阻塞等待鎖的獲取,在高并發(fā)環(huán)境下,容易造成大量請求阻塞,影響系統(tǒng)可用性

          基于redis

          Java jedis分布式鎖例子

          依賴(注意版本2.9.0后,但3以上不支持)


          ????redis.clients
          ????jedis
          ????2.9.0

          public?class?RedisTool?{

          ????private?static?final?String?LOCK_SUCCESS?=?"OK";
          ????private?static?final?String?SET_IF_NOT_EXIST?=?"NX";
          ????private?static?final?String?SET_WITH_EXPIRE_TIME?=?"PX";

          ????/**
          ?????*?嘗試獲取分布式鎖
          ?????*?@param?jedis?Redis客戶端
          ?????*?@param?lockKey?鎖
          ?????*?@param?requestId?請求標識
          ?????*?@param?expireTime?超期時間
          ?????*?@return?是否獲取成功
          ?????*/
          ????public?static?boolean?tryGetDistributedLock(Jedis?jedis,?String?lockKey,?String?requestId,?int?expireTime)?{
          ????????/**
          ?????????*?1.?使用key來當鎖,因為key是唯一的
          ?????????* 2. value,傳的是requestId。通過給value賦值為requestId,我們就知道這把鎖是哪個請求加的了,在解鎖的時候就可以有依據(jù)
          ?????????*?3.?NX,意思是SET?IF?NOT?EXIST,即當key不存在時,我們進行set操作;若key已經(jīng)存在,則不做任何操作;
          ?????????* 4. PX,意思是我們要給這個key加一個過期的設置,具體時間由第五個參數(shù)決定。
          ?????????*?5.?time,代表key的過期時間
          ?????????*/
          ????????String?result?=?jedis.set(lockKey,?requestId,?SET_IF_NOT_EXIST,?SET_WITH_EXPIRE_TIME,?expireTime);

          ????????if?(LOCK_SUCCESS.equals(result))?{
          ????????????return?true;
          ????????}
          ????????return?false;

          ????}
          ????
          ????private?static?final?Long?RELEASE_SUCCESS?=?1L;

          ????/**
          ?????*?釋放分布式鎖
          ?????*?@param?jedis?Redis客戶端
          ?????*?@param?lockKey?鎖
          ?????*?@param?requestId?請求標識
          ?????*?@return?是否釋放成功
          ?????*/
          ????public?static?boolean?releaseDistributedLock(Jedis?jedis,?String?lockKey,?String?requestId)?{
          ????????/**
          ?????????*?使用Lua語言來實現(xiàn),來確保上述操作是原子性。在eval命令執(zhí)行Lua代碼的時候,Lua代碼將被當成一個命令去執(zhí)行,并且直到eval命令執(zhí)行完成,Redis才會執(zhí)行其他命令。
          ?????????*?參數(shù)KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId
          ?????????*/
          ????????String?script?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";
          ????????Object?result?=?jedis.eval(script,?Collections.singletonList(lockKey),?Collections.singletonList(requestId));

          ????????if?(RELEASE_SUCCESS.equals(result))?{
          ????????????return?true;
          ????????}
          ????????return?false;

          ????}

          }

          執(zhí)行上面的set()方法就只會導致兩種結果:

          • 當前沒有鎖(key不存在),那么就進行加鎖操作,并對鎖設置個有效期,同時value表示加鎖的客戶端。

          • 已有鎖存在,不做任何操作。

          Redisson實現(xiàn)分布式鎖

          使用流程如下,創(chuàng)建Redisson實例(單機或哨兵模式),然后通過getLock獲取鎖,后續(xù)是進行l(wèi)ock和unlock操作。

          //?1.?Create?config?object
          Config?config?=?new?Config();
          config.useClusterServers()
          ???????//?use?"rediss://"?for?SSL?connection
          ??????.addNodeAddress("redis://127.0.0.1:7181");
          //?2.?Create?Redisson?instance
          //?Sync?and?Async?API
          RedissonClient?redisson?=?Redisson.create(config);
          //?3.?Get?Redis?based?implementation?of?java.util.concurrent.locks.Lock
          RLock?lock?=?redisson.getLock("myLock");

          具體使用例子可參考:https://www.cnblogs.com/milicool/p/9201271.html

          基于zookeeper

          zookeeper基本鎖原理

          利用臨時節(jié)點與watch機制,每個鎖占用一個普通節(jié)點/lock,當需要獲取鎖時,在/lock目錄下創(chuàng)建一個臨時節(jié)點,創(chuàng)建成功則表示獲取鎖成功,失敗則watch /lock節(jié)點,有刪除操作后再去爭鎖。

          臨時節(jié)點

          • 好處:在于當進程掛掉后能自動上鎖的節(jié)點自動刪除,即取消鎖

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

          zookeeper鎖優(yōu)化原理

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

          步驟:

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

          • 判斷創(chuàng)建的節(jié)點序號是否最小,如果是則獲取鎖成功。不是則獲取鎖失敗,watch序號比本身小的前一個節(jié)點(避免很多線程watch同一個node,導致羊群效應)

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

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

          優(yōu)缺點:

          • 優(yōu)點:有效的解決單點問題,不可重入問題,非阻塞問題,以及鎖無法釋放問題。實現(xiàn)簡單

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

          (圖片來自https://mp.weixin.qq.com/s/jn4LkPKlWJhfUwIKkp3KpQ)

          開源框架Curator

          Curator開源框架對zookeeper分布式鎖進行了實現(xiàn)。具體例子可參考:https://www.jianshu.com/p/31335efec309




          粉絲福利:Java從入門到入土學習路線圖

          ???

          ?長按上方微信二維碼?2 秒


          感謝點贊支持下哈?

          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  wwwx欧美| 一区二区欧美精品 | 亚洲射逼 | 亚洲AV成人无码精电影在线 | 中文字幕无码一区二区三区一本久道不卡 |