庫存扣多了,到底怎么整

2018-09-09 18:07 更新

典型互聯(lián)網(wǎng)架構(gòu)

業(yè)務(wù)復(fù)雜、數(shù)據(jù)量大、并發(fā)量大的業(yè)務(wù)場景下,典型的互聯(lián)網(wǎng)架構(gòu),一般會分為這么幾層:

?調(diào)用層,一般是處于端上的browser或者APP
?站點層,一般是拼裝html或者json返回的web-server層
?服務(wù)層,一般是提供RPC調(diào)用接口的service層
?數(shù)據(jù)層,提供固化數(shù)據(jù)存儲的db

對于庫存業(yè)務(wù),一般有個庫存服務(wù),提供庫存的查詢、扣減、設(shè)置等RPC接口:
庫存服務(wù)
?庫存查詢,stock-service本質(zhì)上執(zhí)行的是
select num from stock where sid=$sid
?庫存扣減,stock-service本質(zhì)上執(zhí)行的是
update stock set num=num-$reduce where sid=$sid
?庫存設(shè)置,stock-service本質(zhì)上執(zhí)行的是
update stock set num=$num_new where sid=$sid

用戶下單前,一般會對庫存進行查詢,有足夠的存量才允許扣減:
庫存查詢
如上圖所示,通過查詢接口,得到庫存是5。

用戶下單時,接著會對庫存進行扣減:
庫存扣減
如上圖所示,購買3單位的商品,通過扣減接口,最終得到庫存是2。

希望設(shè)計往往有容錯機制,例如“重試”,如果通過扣減接口來修改庫存,在重試時,可能會得到錯誤的數(shù)據(jù),導(dǎo)致重復(fù)扣減
庫存容錯機制

如上圖所示,如果數(shù)據(jù)庫層面有重試容錯機制,可能導(dǎo)致一次扣減執(zhí)行兩次,最終得到一個負數(shù)的錯誤庫存。


重試導(dǎo)致錯誤的根本原因,是因為“扣減”操作是一個非冪等的操作,不能夠重復(fù)執(zhí)行,改成設(shè)置操作則不會有這個問題:
庫存冪等操作
如上圖所示,同樣是購買3單位的商品,通過設(shè)置庫存操作,即使有重試容錯機制,也不會得到錯誤的庫存,設(shè)置庫存是一個冪等操作。

在并發(fā)量很大的情況下,還會有其他的問題:
庫存并發(fā)操作
如上圖所示,兩個并發(fā)的操作,查詢庫存,都得到了庫存是5。

接下來用戶發(fā)生了并發(fā)的購買動作(秒殺類業(yè)務(wù)特別容易出現(xiàn)):
庫存并發(fā)操作2
如上圖所示:
?用戶1購買了3個庫存,于是庫存要設(shè)置為2
?用戶2購買了2個庫存,于是庫存要設(shè)置為3
?這兩個設(shè)置庫存的接口并發(fā)執(zhí)行,庫存會先變成2,再變成3,導(dǎo)致數(shù)據(jù)不一致(實際賣出了5件商品,但庫存只扣減了2,最后一次設(shè)置庫存會覆蓋和掩蓋前一次并發(fā)操作

根本原因是,設(shè)置操作發(fā)生的時候,沒有檢查庫存與查詢出來的庫存有沒有變化,理論上:
?庫存為5時,用戶1的庫存設(shè)置才能成功
?庫存為5時,用戶2的庫存設(shè)置才能成功

實際執(zhí)行的時候:
?庫存為5,用戶1的set stock 2確實應(yīng)該成功
?庫存變?yōu)?了,用戶2的set stock 3應(yīng)該失敗掉

升級修改很容易,將庫存設(shè)置接口,stock-service上執(zhí)行的:
update stock set num=$y where sid=$sid
升級為:
update stock set num=$num_new where sid=$sid and num=$num_old
這正是大家常說的“Compare And Set”(CAS),是一種常見的降低讀寫鎖沖突,保證數(shù)據(jù)一致性的方法。

總結(jié)

在業(yè)務(wù)復(fù)雜,數(shù)據(jù)量大,并發(fā)量大的情況下,庫存扣減容易引發(fā)數(shù)據(jù)的不一致,常見的優(yōu)化方案有兩個:
?調(diào)用“設(shè)置庫存”接口,能夠保證數(shù)據(jù)的冪等性
?在實現(xiàn)“設(shè)置庫存”接口時,需要加上原有庫存的比較,才允許設(shè)置成功,能解決高并發(fā)下庫存扣減的一致性問題

希望大伙有收獲。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號