「DB」庫存扣多了,到底怎麼整|架構師之路


「DB」庫存扣多了,到底怎麼整|架構師之路


業務複雜、數據量大、併發量大的業務場景下,典型的互聯網架構,一般會分為這麼幾層:

  • 調用層,一般是處於端上的browser或者APP
  • 站點層,一般是拼裝html或者json返回的web-server層
  • 服務層,一般是提供RPC調用接口的service層
  • 數據層,提供固化數據存儲的db

對於庫存業務,一般有個庫存服務,提供庫存的查詢、扣減、設置等RPC接口:

「DB」庫存扣多了,到底怎麼整|架構師之路


  • 庫存查詢,stock-service本質上執行的是
  • select num from stock where sid=$sid
  • 庫存扣減,stock-service本質上執行的是
  • update stock set num=num-$reduce where sid=$sid
  • 庫存設置,stock-service本質上執行的是
  • update stock set num=$num_new where sid=$sid

用戶下單前,一般會對庫存進行查詢,有足夠的存量才允許扣減:

「DB」庫存扣多了,到底怎麼整|架構師之路


如上圖所示,通過查詢接口,得到庫存是5。

用戶下單時,接著會對庫存進行扣減:

「DB」庫存扣多了,到底怎麼整|架構師之路


如上圖所示,購買3單位的商品,通過扣減接口,最終得到庫存是2。

希望設計往往有容錯機制,例如“重試”,如果通過扣減接口來修改庫存,在重試時,可能會得到錯誤的數據,導致重複扣減:

「DB」庫存扣多了,到底怎麼整|架構師之路


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

重試導致錯誤的根本原因,是因為“扣減”操作是一個非冪等的操作,不能夠重複執行,改成設置操作則不會有這個問題:

「DB」庫存扣多了,到底怎麼整|架構師之路


如上圖所示,同樣是購買3單位的商品,通過設置庫存操作,即使有重試容錯機制,也不會得到錯誤的庫存,設置庫存是一個

冪等操作。

在併發量很大的情況下,還會有其他的問題:

「DB」庫存扣多了,到底怎麼整|架構師之路


如上圖所示,兩個併發的操作,查詢庫存,都得到了庫存是5。

接下來用戶發生了

併發的購買動作(秒殺類業務特別容易出現):

「DB」庫存扣多了,到底怎麼整|架構師之路


如上圖所示:

  • 用戶1購買了3個庫存,於是庫存要設置為2
  • 用戶2購買了2個庫存,於是庫存要設置為3
  • 這兩個設置庫存的接口併發執行,
    庫存會先變成2,再變成3,導致數據不一致(實際賣出了5件商品,但庫存只扣減了2,最後一次設置庫存會覆蓋和掩蓋前一次併發操作)

根本原因是,設置操作發生的時候,沒有檢查庫存與查詢出來的庫存有沒有變化,理論上:

  • 庫存為5時,用戶1的庫存設置才能成功
  • 庫存為5時,用戶2的庫存設置才能成功

實際執行的時候:

  • 庫存為5,用戶1的set stock 2確實應該成功
  • 庫存變為2了,用戶2的set stock 3應該失敗掉

升級修改很容易,將庫存設置接口,stock-service上執行的:

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),是一種常見的降低讀寫鎖衝突,保證數據一致性的方法。

總結

在業務複雜,數據量大,併發量大的情況下,庫存扣減容易引發數據的不一致,常見的優化方案有兩個:

  • 調用“設置庫存”接口,能夠保證數據的冪等性
  • 在實現“設置庫存”接口時,需要加上原有庫存的比較,才允許設置成功,能解決高併發下庫存扣減的一致性問題


希望大夥有收穫。


文章轉載自58沈劍,若有問題請聯繫我刪除。


分享到:


相關文章: