業務場景:
系統A是一個電商系統,目前是一臺機器部署,系統中有一個用戶下訂單的接口,但是用戶下訂單之前一定要去檢查一下庫存,確保庫存足夠了才會給用戶下單。
由于系統有一定的并發,所以會預先將商品的庫存保存在redis中,用戶下單的時候會更新redis的庫存。
此時系統架構如下:
但是這樣一來會產生一個問題:假如某個時刻,redis里面的某個商品庫存為1,此時兩個請求同時到來,其中一個請求執行到上圖的第3步,更新數據庫的庫存為0,但是第4步還沒有執行。
而另外一個請求執行到了第2步,發現庫存還是1,就繼續執行第3步。
這樣的結果,是導致賣出了2個商品,然而其實庫存只有1個。
很明顯不對啊!這就是典型的庫存超賣問題
我們很容易想到解決方案:用鎖把2、3、4步鎖住,讓他們執行完之后,另一個線程才能進來執行第2步。
按照上面的圖,在執行第2步時,使用Java提供的synchronized或者ReentrantLock來鎖住,然后在第4步執行完之后才釋放鎖。
Synchronized(this){
//扣減庫存邏輯代碼
……
}
這樣一來,2、3、4 這3個步驟就被“鎖”住了,多個線程之間只能串行化執行。
但是好景不長,整個系統的并發飆升,一臺機器扛不住了。現在要增加一臺機器,如下圖:
增加機器之后,系統變成上圖所示,我的天!
假設此時兩個用戶的請求同時到來,但是落在了不同的機器上,那么這兩個請求是可以同時執行了,還是會出現庫存超賣的問題。
為什么呢?因為上圖中的兩個A系統,運行在兩個不同的JVM里面,他們加的鎖只對屬于自己JVM里面的線程有效,對于其他JVM的線程是無效的。
因此,這里的問題是:Java提供的原生鎖機制在多機部署場景下失效了
這是因為兩臺機器加的鎖不是同一個鎖(兩個鎖在不同的JVM里面)。
那么,我們只要保證兩臺機器加的鎖是同一個鎖,問題不就解決了嗎?
此時,就該分布式鎖隆重登場了,分布式鎖的思路是:
在整個系統提供一個全局、唯一的獲取鎖的“東西”,然后每個系統在需要加鎖時,都去問這個“東西”拿到一把鎖,這樣不同的系統拿到的就可以認為是同一把鎖。
至于這個“東西”,可以是Redis、Zookeeper,也可以是數據庫。
文字描述不太直觀,我們來看下圖:
通過上面的分析,我們知道了庫存超賣場景在分布式部署系統的情況下使用Java原生的鎖機制無法保證線程安全,所以我們需要用到分布式鎖的方案。
那么,如何實現分布式鎖呢?請看我的下一篇文章