庫存是電商系統的核心環節,如何做到不少賣,不超賣是庫存關心的核心業務問題。業務量大時帶來的問題是如何更快速的處理庫存計算。
此處以最簡模式來討論庫存設計。
以下內容只做分析,不能直接套用,歡迎各位同道前來交流指正
庫存模型:sku,num。
sku是標示商品的唯一編號,num是商品的數量。
訂單處理時需扣減商品庫存。
mysql實現
庫存初始數據:
mysql隔離級別READ-COMMITTED
扣減1001庫存7:
10-7=3;
3>0;
開始扣減庫存
UPDATEstock
SET num=3 WHERE sku=1001;
在串行執行情況下以上邏輯是正確的處理方式。
如果是並發執行,可能會出現這種情況:
10-7=3;
庫存在另外一個線程中被修改為5
UPDATEstock
SET num=3 WHERE sku=1001;
庫存最終被修改為3
一共賣出5+7=12個商品,實際總商品數是10,超賣發生。
為解決超賣引入如下方案:
10-7=3;
3>0;
開始扣減庫存
庫存在另外一個線程中被修改為5
UPDATEstock
SET num=num-7 WHERE num>=7 AND sku=1001;
update失敗,超賣解決。
考慮到訂單的商品有多個,那在並發執行的情況下是否還是正常呢?
現有訂單1,訂單2同時處理,都是1001扣減庫存5,1002扣減庫存10
訂單1:
UPDATE
stock
SET num=num-5 WHERE num>=5 AND sku=1001;
UPDATEstock
SET num=num-10 WHERE num>=10 AND sku=1002;
訂單2:
UPDATE
stock
SET num=num-10 WHERE num>=10 AND sku=1002;
UPDATEstock
SET num=num-5 WHERE num>=5 AND sku=1001;
若sql執行情況是訂單1先修改1001,訂單2修改1002,這個時候產生死鎖,有一個訂單必然會失敗。
少賣發生。
如果是訂單2中第二個商品是1003會怎么樣呢?
兩個訂單的庫存修改會變成串行執行。
這種情況下會帶來性能下降的問題,事務超時的時候會發生多個訂單失敗的情況。
多個訂單失敗后如果訂單依然要處理,此時庫存沒有扣減,又會發生超賣。
結論:mysql可以保證數據一致性和持久性,但是性能不高,在量級較高的情況下庫存並沒有控制好少賣超賣的情況。
redis實現
mysql的缺點是如此顯而易見,為了解決這個問題,現在引入redis。
redis的讀寫速度快,數據操作都在內存中運行,性能必然比mysql高。
string類型提供了decrby方法,可以以原子方式對string做減法。
對1001庫存減5:
decrby 1001 5
獲得返回值,如果小於0則執行
incrby 1001 5
並且所有相關數據回滾
在多線程環境中會有多個訂單同時回滾,庫存充足的情況。產生這種現象后,失敗的訂單可以留待下次再行處理。
但是庫存的值並不能用來做實時計算,因為失敗訂單的庫存沒有進行計算。
redis沒有事務,無法保證數據一致性。
結論:redis解決了性能問題,但是數據一致性無法保證。
其它
為解決mysql問題,可以結合異步定時扣減庫存,隊列做。
最后,不管用mysql還是redis 都各有優缺點.