自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

別吵吵,分布式鎖也是鎖

開發(fā) 開發(fā)工具 分布式
Tomcat是這個系統(tǒng)的核心組成部分, 每當(dāng)有用戶請求過來,Tomcat就會從線程池里找個線程來處理,有的執(zhí)行登錄,有的查看購物車,有的下訂單,看著屬下們盡心盡職地工作,完成人類的請求,Tomcat就很有成就感。

[[250750]]

 Tomcat的鎖

Tomcat是這個系統(tǒng)的核心組成部分, 每當(dāng)有用戶請求過來,Tomcat就會從線程池里找個線程來處理,有的執(zhí)行登錄,有的查看購物車,有的下訂單,看著屬下們盡心盡職地工作,完成人類的請求,Tomcat就很有成就感。

與此同時,它也很得意,所有的業(yè)務(wù)邏輯盡在掌握。MySQL算啥!不就是一個保存數(shù)據(jù)的地方嗎? Redis算啥!不就是一個加快速度的緩存嗎?

沒有他們,我也能找到替代品,而我不可替代的, Tomcat經(jīng)常這么想。

昨天MySQL偶然說起隔壁機(jī)器入駐了一個叫做Node.js的家伙,居然只用一個線程來執(zhí)行JavaScript代碼,實(shí)現(xiàn)各種業(yè)務(wù)邏輯,JavaScript也能到后端來?還用回調(diào)? 這不是胡鬧嗎?不過得小心,別被他把業(yè)務(wù)都給搶走了。

想到此處,Tomcat立刻去查看各個線程活干得怎么樣,有沒有人故意偷懶。

線程0x9527和0x7954又在吵架了,原因非常簡單,他們倆都去做扣減庫存的操作:讀取庫存,修改庫存,寫回?cái)?shù)據(jù)庫。

線程的并發(fā)執(zhí)行導(dǎo)致三個操作交織在了一起,***數(shù)據(jù)出現(xiàn)了不一致。

 

Tomcat說:“你們怎么搞的,為什么要把庫存讀出來,直接update 庫存不行嗎? 讓MySQL老頭兒去保證正確性。要學(xué)會甩鍋啊!”

0x7954回答道:“沒辦法,張大胖的代碼就是這么寫的,好像是業(yè)務(wù)要求的,扣減庫存之前要檢查庫存夠不夠。”

Tomcat一陣牙疼, 不由得想起了Redis的處理辦法, 對于每個讀寫緩存的請求,Redis都把他們給排成了隊(duì),用一個線程挨個去處理,肯定沒有這個并發(fā)的問題了。

可是自己這里不行啊,訪問數(shù)據(jù)庫是極慢的操作,如果只用一個線程,一個個地處理請求,所有的請求都得等待,人類會急死的。

沒辦法,Tomcat扔給他們倆一個Java對象:“這是一把鎖,以后誰先搶到誰才能執(zhí)行扣減庫存的三個操作。”

“如果搶不到怎么辦?”

“阻塞等待,別人釋放了鎖,JVM自然會喚醒你,然后再去搶! 什么時候搶到,什么時候執(zhí)行。”

分布式的鎖

張大胖覺得有點(diǎn)不對勁, 這幾天程序執(zhí)行怎么有點(diǎn)兒慢了呢?

他還以為是機(jī)器性能不夠,就申請了幾臺新機(jī)器,又安裝了幾個Tomcat,組成了一個集群。

 

這下可好,三個Tomcat, 每個Tomcat都有一把鎖來控制對庫存的訪問。

在Tomcat這個JVM進(jìn)程內(nèi)部,同一個時刻只有一個幸運(yùn)兒線程可以扣減庫存,可是現(xiàn)在有三個Tomcat,出現(xiàn)了三個幸運(yùn)兒。

這三個幸運(yùn)兒在扣減庫存的時候,仍然會出現(xiàn)0x7954和0x9527那樣的錯誤,只不過現(xiàn)在他們互不知曉,連吵架的機(jī)會都沒有了。

三個Tomcat都覺得頭大,在這個分布式的環(huán)境中,多個進(jìn)程在運(yùn)行,原來那種進(jìn)程內(nèi)的鎖已經(jīng)失效,當(dāng)務(wù)之急是找一個客觀、公正、獨(dú)立的第三方來實(shí)現(xiàn)鎖的功能。

MySQL提議: “到我這里來找鎖啊!”

“你那里能提供一個鎖服務(wù)? 暴露出來讓我們使用? ” Tomcat A問道。

“不不,不是一個鎖服務(wù),我給你們一個數(shù)據(jù)庫表,這個表中的字段lock_name有個唯一性約束。”

 

“你的意思是,我們的線程每次想獲得鎖的時候,都去數(shù)據(jù)庫插入一條數(shù)據(jù)? ” Tomcat A 反映很快。

  1. insert into locks(lock_name,...) values('stock',...); 

“對啊,我的唯一性約束只能保證一個成功,其他的都失敗,就相當(dāng)于獲得鎖了。 當(dāng)然那個線程的操作完成以后,需要釋放鎖。”

  1. delete from locks where lock_name='stock' 

 

這倒是一個簡單的辦法, 但也是一個重量級的辦法:每次獲得鎖都得訪問一次數(shù)據(jù)庫!

假設(shè)來自TomcatA的0x9527捷足先登,插入了一條數(shù)據(jù),獲得了鎖, 那來自Tomcat B的0x7954操作肯定失敗,這時候0x7954該怎么辦? 能阻塞等待TomcatB來喚醒他嗎? 不行,因?yàn)檫BTomcatB 都不知道0x9527什么時候操作完成, 除非MySQL來通知各個Tomcat, 這是肯定不行的。

那0x7954@TomcatB只能做一件事情: 等待一會兒,然后重試! 如此循環(huán)下去,直到獲得鎖為止。

可是如果0x9527獲得了鎖,在執(zhí)行的過程中TomcatA 掛掉了,那數(shù)據(jù)庫記錄一直存在,無人刪除,那鎖就永遠(yuǎn)也無法釋放了! 還得弄一個清理者, 清理那些過期沒釋放的鎖, 這實(shí)在是太麻煩了。

Redis

這時候Redis說道:“千萬別上MySQL的賊船!他的辦法太笨重了,不就是找個第三方來保存鎖的信息嗎? 用我的緩存多好!”

“Redis這小子操作的是內(nèi)存,速度會快很多!” Tomcat B說道。

“對,MySQL不是給你們提供了一張表讓你們插入數(shù)據(jù)嗎? 我這里不用那么麻煩,你們Tomcat的線程,都可以嘗試到我的緩存中設(shè)置一個值,比如stock_lock=true, 誰先設(shè)置成功,誰就獲得了鎖,可以去扣減庫存。”

 

“ 如果有多個線程去設(shè)置,你能保證只有一個成功,別的都失敗嗎? ”

Redis拍拍胸脯: “絕對保證!”

(碼農(nóng)翻身老劉注:其實(shí)就是setnx命令了)

MySQL撇撇嘴:“和我的方案本質(zhì)上是一樣的。人家Tomcat 的線程對庫存做了修改以后,也還得去解鎖,去刪除這個stock_lock。”

Redis說:“我這里還能設(shè)置過期時間,如果Tomcat A上線程獲得了鎖,然后Tomcat A掛掉了, 到了過期時間,我就可以自動把這個stock_lock刪除,別的線程又可以獲得鎖了!”

“嗯,是比MySQL先進(jìn),并且速度更快,我們還是用這個鎖吧。” 三個Tomcat都表示同意。

定期自動釋放的問題

“且慢,這個自動刪除過期的鎖有問題啊 !” MySQL突然反擊。

“什么問題?” Redis沒想到數(shù)據(jù)庫老頭兒還想負(fù)隅頑抗。

“假設(shè)Tomcat A上的0x9527獲得了鎖, 去執(zhí)行扣減庫存的操作,然后由于某種原因被阻塞了,阻塞的時間超過了過期時間,鎖被你釋放掉了,最終還是會出現(xiàn)不一致!”

 

“你這是吹毛求疵,絕對是小概率事件!” Redis叫道!

“再說了,用你的數(shù)據(jù)庫方案,也得定期清理那些鎖,道理是一樣的。”

行鎖

第二天, MySQL高興得去找Tomcat:“兄弟們,我昨天晚上和Quartz(一個著名的定時執(zhí)行框架)聊了半宿,他告訴了我一個新的用數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的辦法, 行鎖。”

 

“看到?jīng)]有, 通過添加一個for udpate ,這個SQL語句會把這一行給鎖定,就是獲得了鎖! 只要事務(wù)一提交,這個行鎖就自動釋放了。”

“那沒有獲得鎖的別的線程呢? ”

“自然是阻塞住了,等到別的線程釋放了行鎖,它可以自動去獲取,代碼中都不用循環(huán)重試,你看,之前的方案都做不到這一點(diǎn)吧。” MySQL說道。

“那要是有個線程遲遲不釋放行鎖,會發(fā)生什么問題?” Tomcat最關(guān)心這個。

“那其他線程都會等待,并且占用著數(shù)據(jù)庫連接不釋放,嗯,如果連接被占用得過多,連接池就要出問題了......” MySQL底氣不足了,這可是個致命的問題。

“哈哈,看你出的什么餿注意!還是用我的鎖吧!” Redis笑道。

“那人家Quartz為什么可以用?”MySQL不死心。

“估計(jì)Quartz業(yè)務(wù)單一,并且鎖釋放得很快,不會出問題吧。”

CAS

正在這時,Node.js悄悄地走過來, 把數(shù)據(jù)庫老頭兒拉走了:“前輩,別給他們一般見識,不就是扣減庫存嗎,用啥分布式鎖!, 咱們這么做:”

#old_num = 先獲取現(xiàn)有的庫存數(shù)量

  1. #new_num = #old_num - 10  
  2. update stock set stock_num = #new_num where product_id=#product_id and stock_num = #old_num 

MySQL眼前一亮, 是啊,每次把這個#old_num 作為條件傳進(jìn)去調(diào)用update語句,如果能成功,說明在這段時間內(nèi)沒有別的線程更新庫存;

如果不成功,那就重新執(zhí)行這三條語句,直到成功為止, 就這個么簡單, 完全不用鎖,真是太爽了。

過了幾天,Tomcat他們也聽說了這個方案, 驚訝地說:“這不就是我們Java常用的Compare And Set(CAS)嗎?”

總結(jié)

與此同時,張大胖開始做總結(jié):分布式鎖和進(jìn)程內(nèi)的鎖本質(zhì)上是一樣的。

1. 要互斥,同一時刻只能被一臺機(jī)器上的一個線程獲得。

2. ***支持阻塞,然后喚醒,這樣那些等待的線程不用循環(huán)重試。

3. ***可以重入(本文沒有涉及,參見《編程世界的那把鎖》)

4. 獲得鎖和釋放鎖速度要快

5. 對于分布式鎖,需要找到一個集中的“地方”(數(shù)據(jù)庫,Redis, Zookeeper等)來保存鎖,這個地方***是高可用的。

6. 考慮到“不可靠的”分布式環(huán)境, 分布式鎖需要設(shè)定過期時間

7. CAS的思想很重要。

【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號coderising獲取授權(quán)】

 

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2021-11-26 06:43:19

Java分布式

2019-06-19 15:40:06

分布式鎖RedisJava

2021-07-06 08:37:29

Redisson分布式

2021-07-16 07:57:34

ZooKeeperCurator源碼

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2022-08-04 08:45:50

Redisson分布式鎖工具

2021-07-02 08:51:09

Redisson分布式鎖公平鎖

2021-06-30 14:56:12

Redisson分布式公平鎖

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2023-09-22 08:00:00

分布式鎖Redis

2017-10-24 11:28:23

Zookeeper分布式鎖架構(gòu)

2023-08-21 19:10:34

Redis分布式

2021-10-25 10:21:59

ZK分布式鎖ZooKeeper

2018-07-17 08:14:22

分布式分布式鎖方位

2024-11-28 15:11:28

2021-07-01 09:42:08

Redisson分布式

2020-11-16 12:55:41

Redis分布式鎖Zookeeper

2022-09-19 08:17:09

Redis分布式

2012-02-28 10:30:56

C#.NET

2024-07-29 09:57:47

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號