搞懂Java分布式鎖實現(xiàn)看這篇文章就對了

前言:

隨著微處理機技術(shù)的發(fā)展,人們只需花幾百美元就能買到一個CPU芯片燥翅,這個芯片每秒鐘執(zhí)行的指令比80年代最大的大型機的處理機每秒鐘所執(zhí)行的指令還多。如果你愿意付出兩倍的價錢瞧掺,將得到同樣的CPU乎串,但它卻以更高的時鐘速率運行。因此买置,最節(jié)約成本的辦法通常是在一個系統(tǒng)中使用集中在一起的大量的廉價CPU寄悯。所以,傾向于分布式系統(tǒng)的主要原因是它可以潛在地得到比單個的大型集中式系統(tǒng)好得多的性價比堕义。實際上猜旬,分布式系統(tǒng)是通過較低廉的價格來實現(xiàn)相似的性能的脆栋。

隨著互聯(lián)網(wǎng)的興起,越來越多的人使用者互聯(lián)網(wǎng)產(chǎn)品洒擦。一般互聯(lián)網(wǎng)系統(tǒng)都是分布式部署的椿争,分布式部署確實能帶來性能和效率上的提升,提升效率的同事熟嫩,我們還需要注意秦踪,保證一個分布式環(huán)境下數(shù)據(jù)一致性的問題。

分布式鎖簡述

在單機時代掸茅,雖然不存在分布式鎖椅邓,但也會面臨資源互斥的情況,只不過在單機的情況下昧狮,如果有多個線程要同時訪問某個共享資源的時候景馁,我們可以采用線程間加鎖的機制,即當某個線程獲取到這個資源后逗鸣,就需要對這個資源進行加鎖合住,當使用完資源之后,再解鎖撒璧,其它線程就可以接著使用了透葛。例如,在JAVA中卿樱,甚至專門提供了一些處理鎖機制的一些API(synchronize/Lock等)僚害。

但是到了分布式系統(tǒng)的時代,這種線程之間的鎖機制繁调,就沒作用了贡珊,系統(tǒng)可能會有多份并且部署在不同的機器上,這些資源已經(jīng)不是在線程之間共享了涉馁,而是屬于進程之間共享的資源门岔。因此,為了解決這個問題烤送,「分布式鎖」就強勢登場了寒随。

分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。在分布式系統(tǒng)中帮坚,常常需要協(xié)調(diào)他們的動作妻往。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候试和,往往需要互斥來防止彼此干擾來保證一致性讯泣,在這種情況下,便需要使用到分布式鎖阅悍。

在分布式系統(tǒng)中好渠,常常需要協(xié)調(diào)他們的動作昨稼。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候拳锚,往往需要互斥來防止彼此干擾來保證一致性假栓,這個時候,便需要使用到分布式鎖霍掺。

分布式鎖要滿足哪些要求呢匾荆?

  • 排他性: 在同一時間只會有一個客戶端能獲取到鎖,其它客戶端無法同時獲取
  • 避免死鎖: 這把鎖在一段有限的時間之后杆烁,一定會被釋放(正常釋放或異常釋放)
  • 高可用: 獲取或釋放鎖的機制必須高可用且性能佳
  • ...

目前相對主流的有三種牙丽,從實現(xiàn)的復雜度上來看,從上往下難度依次增加:

  • 數(shù)據(jù)庫(MySQL)
  • Redis
  • ZooKeeper

v 基于數(shù)據(jù)庫實現(xiàn)

基于數(shù)據(jù)庫來做分布式鎖的話兔魂,通常有兩種做法:

  • 基于數(shù)據(jù)庫的樂觀鎖
  • 基于數(shù)據(jù)庫的悲觀鎖

樂觀鎖

樂觀鎖的特點先進行業(yè)務操作烤芦,不到萬不得已不去拿鎖。即“樂觀”的認為拿鎖多半是會成功的入热,因此在進行完業(yè)務操作需要實際更新數(shù)據(jù)的最后一步再去拿一下鎖就好拍棕。

樂觀鎖機制其實就是在數(shù)據(jù)庫表中引入一個版本號(version)字段來實現(xiàn)的晓铆。當我們要從數(shù)據(jù)庫中讀取數(shù)據(jù)的時候勺良,同時把這個version字段也讀出來,如果要對讀出來的數(shù)據(jù)進行更新后寫回數(shù)據(jù)庫骄噪,則需要將version加1尚困,同時將新的數(shù)據(jù)與新的version更新到數(shù)據(jù)表中,且必須在更新的時候同時檢查目前數(shù)據(jù)庫里version值是不是之前的那個version链蕊,如果是事甜,則正常更新。如果不是滔韵,則更新失敗逻谦,說明在這個過程中有其它的進程去更新過數(shù)據(jù)了。

看圖敘事陪蜻。模擬實戰(zhàn)場景邦马。

如上圖,故事男主人公(以下簡稱男主)打算去ATM機取3000元宴卖,故事女主人公(以下簡稱女主)則要在某寶買買買滋将,買個包需要3000元,賬戶的余額是5000元症昏。如果沒有采用鎖的話随闽,在兩人同時取款和買買買,可能會出現(xiàn)合計消費了6000肝谭,導致賬戶余額異常掘宪。所以需要用到鎖的機制蛾扇,當男主女主甚至更多小主同時消費時,除了讀取到6000的賬戶余額外添诉,還需要讀取到當前的版本號version=1屁桑,等先行消費成功的主人公(無論誰先消費)去出發(fā)修改賬戶余額的同時,會觸發(fā)version=version+1栏赴,即version=2蘑斧。那么其他人使用未更新的version(1)去更新賬戶余額時就會發(fā)現(xiàn)版本號不對,就會導致本次更新失敗须眷,就得重新去讀取最新賬戶余額以及版本號竖瘾。

樂觀鎖遵循的兩點法則:

  • 鎖服務要有遞增的版本號version
  • 每次更新數(shù)據(jù)的時候都必須先判斷版本號對不對,然后再寫入新的版本號

悲觀鎖

悲觀鎖的特點是先獲取鎖花颗,再進行業(yè)務操作捕传,即“悲觀”的認為獲取鎖是非常有可能失敗的,因此要先確保獲取鎖成功再進行業(yè)務操作扩劝。

通常所說的“一鎖二查三更新”即指的是使用悲觀鎖庸论。通常來講在數(shù)據(jù)庫上的悲觀鎖需要數(shù)據(jù)庫本身提供支持,即通過常用的 select ... for update 操作來實現(xiàn)悲觀鎖棒呛。當數(shù)據(jù)庫執(zhí)行 select for update 時會獲取被 select 中的數(shù)據(jù)行的行鎖聂示,因此其他并發(fā)執(zhí)行的 select for update 如果試圖選中同一行則會發(fā)生排斥(需要等待行鎖被釋放),因此達到鎖的效果簇秒。 select for update 獲取的行鎖會在當前事務結(jié)束時自動釋放鱼喉,因此必須在事務中使用。

示例:

/** * 消費以后更新銀行余額 * @param bankId 銀行卡號 * @param cost 消費金額 * @return */ public boolean consume(Long bankId, Integer cost){ //先鎖定銀行賬戶 BankAccount product = query("SELECT * FROM bank_account WHERE bank_id=#{bankId} FOR UPDATE", bankId); if (product.getNumber() > 0) { int updateCnt = update("UPDATE tb_product_stock SET number=#{cost} WHERE product_id=#{productId}", cost, bankId); if(updateCnt > 0){ //更新庫存成功 return true; } } return false; } 

樂觀鎖與悲觀鎖的區(qū)別

樂觀鎖的思路一般是表中增加版本字段趋观,更新時where語句中增加版本的判斷扛禽,算是一種CAS(Compare And Swep)操作,銀行消費場景中version起到了版本控制的作用( AND version=#{version} )皱坛。

悲觀鎖之所以是悲觀编曼,在于他認為本次操作會發(fā)生并發(fā)沖突,所以一開始就對銀行賬戶加上鎖( SELECT ... FOR UPDATE )剩辟,然后就可以安心的做判斷和更新掐场,因為這時候不會有別人更新賬戶余額。

v 基于Redis實現(xiàn)

基于Redis實現(xiàn)的鎖機制抹沪,主要是依賴redis自身的原子操作刻肄,例如:

SET user_key user_value NX PX 100

redis從2.6.12版本開始,SET命令才支持這些參數(shù):

NX:只在在鍵不存在時融欧,才對鍵進行設置操作敏弃, SET key value NX 效果等同于 SETNX key value

PX millisecond:設置鍵的過期時間為millisecond毫秒,當超過這個時間后噪馏,設置的鍵會自動失效

上述代碼示例是指麦到,當redis中不存在user_key這個鍵的時候绿饵,才會去設置一個user_key鍵,并且給這個鍵的值設置為 user_value瓶颠,且這個鍵的存活時間為100ms

為什么這個命令可以幫我們實現(xiàn)鎖機制呢拟赊?

因為這個命令是只有在某個key不存在的時候,才會執(zhí)行成功粹淋。那么當多個進程同時并發(fā)的去設置同一個key的時候吸祟,就永遠只會有一個進程成功。當某個進程設置成功之后桃移,就可以去執(zhí)行業(yè)務邏輯了屋匕,等業(yè)務邏輯執(zhí)行完畢之后,再去進行解鎖借杰。

解鎖很簡單过吻,只需要刪除這個key就可以了,不過刪除之前需要判斷蔗衡,這個key對應的value是當初自己設置的那個纤虽。

另外,針對redis集群模式的分布式鎖绞惦,可以采用redis的 Redlock (可能會被墻)機制逼纸。

v 基于ZooKeeper實現(xiàn)

其實基于ZooKeeper,就是使用它的臨時有序節(jié)點來實現(xiàn)的分布式鎖翩隧。

原理

當某客戶端要進行邏輯的加鎖時樊展,就在zookeeper上的某個指定節(jié)點的目錄下呻纹,去生成一個唯一的臨時有序節(jié)點堆生, 然后判斷自己是否是這些有序節(jié)點中序號最小的一個,如果是雷酪,則算是獲取了鎖淑仆。如果不是,則說明沒有獲取到鎖哥力,那么就需要在序列中找到比自己小的那個節(jié)點蔗怠,并對其調(diào)用 exist() 方法,對其注冊事件監(jiān)聽吩跋,當監(jiān)聽到這個節(jié)點被刪除了寞射,那就再去判斷一次自己當初創(chuàng)建的節(jié)點是否變成了序列中最小的。如果是锌钮,則獲取鎖桥温,如果不是,則重復上述步驟梁丘。

當釋放鎖的時候侵浸,只需將這個臨時節(jié)點刪除即可旺韭。

如上圖,locker是一個持久節(jié)點掏觉, node_1/node_2/.../node_n 就是上面說的臨時節(jié)點区端,由客戶端client去創(chuàng)建的。

client_1/client_2/.../clien_n 都是想去獲取鎖的客戶端澳腹。以client_1為例织盼,它想去獲取分布式鎖,則需要跑到locker下面去創(chuàng)建臨時節(jié)點(假如是node_1)創(chuàng)建完畢后酱塔,看一下自己的節(jié)點序號是否是locker下面最小的悔政,如果是,則獲取了鎖延旧。如果不是谋国,則去找到比自己小的那個節(jié)點(假如是node_2),找到后迁沫,就監(jiān)聽node_2芦瘾,直到node_2被刪除,那么就開始再次判斷自己的node_1是不是序列中最小的集畅,如果是近弟,則獲取鎖,如果還不是挺智,則繼續(xù)找一下一個節(jié)點祷愉。

總結(jié):

分布式鎖有很多種,開篇說的"相對主流的有三種"只是針對我所遇到的赦颇。分布式鎖未來肯定是千變?nèi)f化的二鳄,無論你身處一個什么樣的公司,最開始的工作可能都得盡可能的從簡單的做起媒怯。希望大家能根據(jù)所在公司業(yè)務場景订讼,選擇適合所在項目的方案。

順便在此給大家推薦一個Java架構(gòu)方面的交流學習群:957734884扇苞,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring欺殿,MyBatis,Netty源碼分析鳖敷,高并發(fā)脖苏、高性能、分布式定踱、微服務架構(gòu)的原理棍潘,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系,主要針對Java開發(fā)人員提升自己,突破瓶頸蜒谤,相信你來學習山宾,會有提升和收獲。在這個群里會有你需要的內(nèi)容 朋友們請抓緊時間加入進來吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鳍徽,一起剝皮案震驚了整個濱河市资锰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阶祭,老刑警劉巖绷杜,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異濒募,居然都是意外死亡鞭盟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進店門瑰剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來齿诉,“玉大人,你說我怎么就攤上這事晌姚≡辆纾” “怎么了?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵挥唠,是天一觀的道長抵恋。 經(jīng)常有香客問我,道長宝磨,這世上最難降的妖魔是什么弧关? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮唤锉,結(jié)果婚禮上世囊,老公的妹妹穿的比我還像新娘。我一直安慰自己腌紧,他們只是感情好茸习,可當我...
    茶點故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布畜隶。 她就那樣靜靜地躺著壁肋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪籽慢。 梳的紋絲不亂的頭發(fā)上浸遗,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天,我揣著相機與錄音箱亿,去河邊找鬼跛锌。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的髓帽。 我是一名探鬼主播菠赚,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼郑藏!你這毒婦竟也來了衡查?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤必盖,失蹤者是張志新(化名)和其女友劉穎拌牲,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歌粥,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡塌忽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了失驶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片土居。...
    茶點故事閱讀 38,712評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嬉探,靈堂內(nèi)的尸體忽然破棺而出装盯,到底是詐尸還是另有隱情,我是刑警寧澤甲馋,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布埂奈,位于F島的核電站,受9級特大地震影響定躏,放射性物質(zhì)發(fā)生泄漏账磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一痊远、第九天 我趴在偏房一處隱蔽的房頂上張望垮抗。 院中可真熱鬧,春花似錦碧聪、人聲如沸冒版。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辞嗡。三九已至,卻和暖如春滞造,著一層夾襖步出監(jiān)牢的瞬間续室,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工谒养, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挺狰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像丰泊,于是被迫代替她去往敵國和親薯定。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,606評論 2 350

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