業(yè)務(wù)場(chǎng)景:
系統(tǒng)A是一個(gè)電商系統(tǒng),目前是一臺(tái)機(jī)器部署,系統(tǒng)中有一個(gè)用戶下訂單的接口熙卡,但是用戶下訂單之前一定要去檢查一下庫(kù)存,確保庫(kù)存足夠了才會(huì)給用戶下單励饵。
由于系統(tǒng)有一定的并發(fā)驳癌,所以會(huì)預(yù)先將商品的庫(kù)存保存在redis中,用戶下單的時(shí)候會(huì)更新redis的庫(kù)存役听。
此時(shí)系統(tǒng)架構(gòu)如下:
但是這樣一來(lái)會(huì)產(chǎn)生一個(gè)問(wèn)題:假如某個(gè)時(shí)刻颓鲜,redis里面的某個(gè)商品庫(kù)存為1,此時(shí)兩個(gè)請(qǐng)求同時(shí)到來(lái)典予,其中一個(gè)請(qǐng)求執(zhí)行到上圖的第3步甜滨,更新數(shù)據(jù)庫(kù)的庫(kù)存為0,但是第4步還沒(méi)有執(zhí)行瘤袖。
而另外一個(gè)請(qǐng)求執(zhí)行到了第2步衣摩,發(fā)現(xiàn)庫(kù)存還是1,就繼續(xù)執(zhí)行第3步捂敌。
這樣的結(jié)果艾扮,是導(dǎo)致賣出了2個(gè)商品,然而其實(shí)庫(kù)存只有1個(gè)占婉。
很明顯不對(duì)袄该臁!這就是典型的庫(kù)存超賣問(wèn)題
我們很容易想到解決方案:用鎖把2锐涯、3磕诊、4步鎖住,讓他們執(zhí)行完之后纹腌,另一個(gè)線程才能進(jìn)來(lái)執(zhí)行第2步霎终。
按照上面的圖,在執(zhí)行第2步時(shí)升薯,使用Java提供的synchronized或者ReentrantLock來(lái)鎖住莱褒,然后在第4步執(zhí)行完之后才釋放鎖。
Synchronized(this){
//扣減庫(kù)存邏輯代碼
……
}
這樣一來(lái)涎劈,2广凸、3、4 這3個(gè)步驟就被“鎖”住了蛛枚,多個(gè)線程之間只能串行化執(zhí)行谅海。
但是好景不長(zhǎng),整個(gè)系統(tǒng)的并發(fā)飆升蹦浦,一臺(tái)機(jī)器扛不住了∨び酰現(xiàn)在要增加一臺(tái)機(jī)器,如下圖:
增加機(jī)器之后,系統(tǒng)變成上圖所示侥袜,我的天蝌诡!
假設(shè)此時(shí)兩個(gè)用戶的請(qǐng)求同時(shí)到來(lái),但是落在了不同的機(jī)器上枫吧,那么這兩個(gè)請(qǐng)求是可以同時(shí)執(zhí)行了浦旱,還是會(huì)出現(xiàn)庫(kù)存超賣的問(wèn)題。
為什么呢九杂?因?yàn)樯蠄D中的兩個(gè)A系統(tǒng)颁湖,運(yùn)行在兩個(gè)不同的JVM里面,他們加的鎖只對(duì)屬于自己JVM里面的線程有效尼酿,對(duì)于其他JVM的線程是無(wú)效的。
因此植影,這里的問(wèn)題是:Java提供的原生鎖機(jī)制在多機(jī)部署場(chǎng)景下失效了
這是因?yàn)閮膳_(tái)機(jī)器加的鎖不是同一個(gè)鎖(兩個(gè)鎖在不同的JVM里面)裳擎。
那么,我們只要保證兩臺(tái)機(jī)器加的鎖是同一個(gè)鎖思币,問(wèn)題不就解決了嗎鹿响?
此時(shí),就該分布式鎖隆重登場(chǎng)了谷饿,分布式鎖的思路是:
在整個(gè)系統(tǒng)提供一個(gè)全局惶我、唯一的獲取鎖的“東西”,然后每個(gè)系統(tǒng)在需要加鎖時(shí)博投,都去問(wèn)這個(gè)“東西”拿到一把鎖绸贡,這樣不同的系統(tǒng)拿到的就可以認(rèn)為是同一把鎖。
至于這個(gè)“東西”毅哗,可以是Redis听怕、Zookeeper,也可以是數(shù)據(jù)庫(kù)虑绵。
文字描述不太直觀尿瞭,我們來(lái)看下圖:
通過(guò)上面的分析,我們知道了庫(kù)存超賣場(chǎng)景在分布式部署系統(tǒng)的情況下使用Java原生的鎖機(jī)制無(wú)法保證線程安全翅睛,所以我們需要用到分布式鎖的方案声搁。
那么,如何實(shí)現(xiàn)分布式鎖呢捕发?請(qǐng)看我的下一篇文章