解決秒殺系統超賣問題的三種方案

在秒殺系統設計中,超賣是一個經典、常見的問題,任何商品都會有數量上限,如何避免成功下訂單買到商品的人數不超過商品數量的上限,這是每個搶購活動都要面臨的難點。

一、問題描述

在多個用戶同時發起對同一個商品的下單請求時,先查詢商品庫存,再修改商品庫存,會出現資源競爭問題,導致庫存的最終結果出現異常。問題:
當商品A一共有庫存15件,用戶甲先下單10件,用戶乙下單8件,這時候庫存只能滿足一個人下單成功,如果兩個人同時提交,就出現了超賣的問題。
在這裏插入圖片描述

二、解決的三種方案

1.解決方案1

  • 悲觀鎖
    當查詢某條記錄時,即讓數據庫爲該記錄加鎖,鎖住記錄後別人無法操作,使用類似如下語法:
select stock from tb_sku where id=1 for update;

SKU.objects.select_for_update().get(id=1)在這裏插入代碼片
  • 悲觀鎖類似於我們在多線程資源競爭時添加的互斥鎖,容易出現死鎖現象,採用不多。

2.解決方案2

  • 樂觀鎖
    樂觀鎖並不是真實存在的鎖,而是在更新的時候判斷此時的庫存是否是之前查詢出的庫存,如果相同,表示沒人修改,可以更新庫存,否則表示別人搶過資源,不再執行庫存更新。類似如下操作:
update tb_sku set stock=2 where id=1 and stock=7;

SKU.objects.filter(id=1, stock=7).update(stock=2)
  • 使用樂觀鎖需修改數據庫的事務隔離級別:
    使用樂觀鎖的時候,如果一個事務修改了庫存並提交了事務,那其他的事務應該可以讀取到修改後的數據值,所以不能使用可重複讀的隔離級別,應該修改爲讀取已提交(Read committed)。
    修改方式:
    在這裏插入圖片描述
    在這裏插入圖片描述

3.解決方案3

  • 任務隊列
    將下單的邏輯放到任務隊列中(如celery),將並行轉爲串行,所有人排隊下單。比如開啓只有一個進程的Celery,一個訂單一個訂單的處理。

三、網上超賣問題解決方案節選

節選自如何解決秒殺系統的性能問題和超賣的討論的片段:如何解決秒殺系統的性能問題和超賣的討論

解決方案2:
  引入隊列,然後將所有寫DB操作在單隊列中排隊,完全串行處理。當達到庫存閥值的時候就不在消費隊列,並關閉購買功能。這就解決了超賣問題。
  優點:解決超賣問題,略微提升性能。
  缺點:性能受限於隊列處理機處理性能和DB的寫入性能中最短的那個,另外多商品同時搶購的時候需要準備多條隊列。

解決方案3:
  將寫操作前移到Memcached中,同時利用Memcached的輕量級的鎖機制CAS來實現減庫存操作。
  優點:讀寫在內存中,操作性能快,引入輕量級鎖之後可以保證同一時刻只有一個寫入成功,解決減庫存問題。
  缺點:沒有實測,基於CAS的特性不知道高併發下是否會出現大量更新失敗?不過加鎖之後肯定對併發性能會有影響。

解決方案4:
  將提交操作變成兩段式,先申請後確認。然後利用Redis的原子自增操作(相比較MySQL的自增來說沒有空洞),同時利用Redis的事務特性來發號,保證拿到小於等於庫存閥值的號的人都可以成功提交訂單。然後數據異步更新到DB中。
  優點:解決超賣問題,庫存讀寫都在內存中,故同時解決性能問題。
  缺點:由於異步寫入DB,可能存在數據不一致。另可能存在少買,也就是如果拿到號的人不真正下訂單,可能庫存減爲0,但是訂單數並沒有達到庫存閥值。

發佈了56 篇原創文章 · 獲贊 73 · 訪問量 2870
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章