淺析「扣減庫存」的方案設(shè)計
今天我們來探討下扣減庫存的方案。
生活中,我們總是用各種電商 APP 搶購商品,但是庫存數(shù)是很少的,特別是秒殺場景,商品可能就一件,那如何保證不會出現(xiàn)超賣的情況呢?
一、扣減庫存的三種方案
1.1 下單減庫存
用戶下單時減庫存
優(yōu)點(diǎn):實(shí)時減庫存,避免付款時因庫存不足減庫存的問題
缺點(diǎn):惡意買家大量下單,將庫存用完,但是不付款,真正想買的人買不到
1.2 付款減庫存
下單頁面顯示最新的庫存,下單時不會立即減庫存,而是等到支付時才會減庫存。
優(yōu)點(diǎn):防止惡意買家大量下單用光庫存,避免下單減庫存的缺點(diǎn)
缺點(diǎn):下單頁面顯示的庫存數(shù)可能不是最新的庫存數(shù),而庫存數(shù)用完后,下單頁面的庫存數(shù)沒有刷新,出現(xiàn)下單數(shù)超過庫存數(shù),若支付的訂單數(shù)超過庫存數(shù),則會出現(xiàn)支付失敗。
1.3 預(yù)扣庫存
下單頁面顯示最新的庫存,下單后保留這個庫存一段時間(比如10分鐘),超過保留時間后,庫存釋放。若保留時間過后再支付,如果沒有庫存,則支付失敗。
優(yōu)點(diǎn):結(jié)合下單減庫存的優(yōu)點(diǎn),實(shí)時減庫存,且緩解惡意買家大量下單的問題,保留時間內(nèi)未支付,則釋放庫存。
缺點(diǎn):保留時間內(nèi),惡意買家大量下單將庫存用完。并發(fā)量很高的時候,依然會出現(xiàn)下單數(shù)超過庫存數(shù)。
二、如何解決惡意買家下單的問題
這里的惡意買家指短時間內(nèi)大量下單,將庫存用完的買家。
2.1 限制用戶下單數(shù)量
優(yōu)點(diǎn):限制惡意買家下單
缺點(diǎn):用戶想要多買幾件,被限制了,會降低銷售量
2.2 標(biāo)識惡意買家
通過標(biāo)識用戶的設(shè)備 id 或者會員 id,將用戶加入黑名單,不足之處是有些用戶是模擬的,識別不出來是不是真正的惡意買家。
三、如何解決下單成功而支付失敗(庫存不足)的問題
3.1 備用庫存
商品庫存用完后,如果還有用戶支付,直接扣減備用庫存。
優(yōu)點(diǎn):緩解部分用戶支付失敗的問題。
缺點(diǎn):備用庫存只能緩解問題,不能從根本上解決問題。另外備用庫存針對普通商品可以,針對特殊商品這種庫存少的,備用庫存量也不會很大,還是會出現(xiàn)大量用戶下單成功卻因庫存不足而支付失敗的問題。
四、如何解決高并發(fā)下庫存超賣的場景
庫存超賣最簡單的解釋就是多成交了訂單而發(fā)不了貨。
場景
用戶 A 和 B 成功下單,在支付時扣減庫存,當(dāng)前庫存數(shù)為 10。因 A 和 B 查詢庫存時,都還有庫存數(shù),所以 A 和 B 都可以付款。
A 和 B 同時支付,A 和 B 支付完成后,可以看做兩個請求回調(diào)后臺系統(tǒng)扣減庫存,有兩個線程處理請求,兩個線程查詢出來的庫存數(shù) inventory = 10。
然后 A 線程更新最終庫存數(shù) :
- lastInventory = inventory - 1 = 9,
B 線程更新庫存數(shù):
- lastInventory = inventory - 1 = 9。
而實(shí)際最終的庫存應(yīng)是 8 才對,這樣就出現(xiàn)庫存超賣的情況,而發(fā)不出貨。
那如何解決庫存超賣的情況呢?
以下方案都是基于數(shù)據(jù)庫層面的。有些同學(xué)可能會問,是不是可以用 Redis 分布式鎖來,后面會講到。
方案一
SQL語句直接更新庫存,而不是先查詢出來,然后賦值
- UPDATE [庫存表] SET 庫存數(shù) - 1
方案二
SQL語句更新庫存時,如果扣減庫存后,庫存數(shù)為負(fù)數(shù),直接拋異常,利用事務(wù)的原子性進(jìn)行自動回滾。
方案三
利用SQL語句更新庫存,防止庫存為負(fù)數(shù)
- UPDATE [庫存表] SET 庫存數(shù) - 1 WHERE 庫存數(shù) - 1 > 0
如果影響條數(shù)大于1,則表示扣減庫存成功,否則不更新庫存,并退款。
五、秒殺場景下如何扣減庫存
5.1 采用下單減庫存
因秒殺場景下,大部分用戶都是想直接購買商品的,可以直接用下單減庫存。
大量用戶和惡意用戶都是同時進(jìn)行的,區(qū)別是正常用戶會直接購買商品,惡意用戶雖然在競爭搶購的名額,但是獲取到的資格和普通用戶一樣,所以下單減庫存在秒殺場景下,惡意用戶下單并不能造成之前說的缺點(diǎn)。
而且下單直接扣減庫存,這個方案更簡單,在第一步就扣減庫存了。
5.2 Redis 緩存
查詢緩存要比查詢數(shù)據(jù)庫快,所以將庫存數(shù)放在緩存中,直接在緩存中扣減庫存。
5.3 限流
秒殺場景中,對請求做了很多限流操作,比如前端頁面的限流和后端令牌桶限流,真正到扣減庫存那一步時,請求數(shù)很少了。所以限流常用在秒殺方案中,感覺可以再寫一篇限流的文章了~
另外其實(shí)真實(shí)的項(xiàng)目中,用到了更多的機(jī)制來保證能夠正常扣減庫存,本篇是拋磚引入,希望大家提出寶貴的建議和方案~。
贈送一張秒殺場景方案總結(jié):
本文轉(zhuǎn)載自微信公眾號「悟空聊架構(gòu)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系悟空聊架構(gòu)眾號。