商城商品超卖处理
首先環(huán)境介紹下:商城商品可能存在幾個端(PC、APP),其次每個端對應(yīng)的服務(wù)端又可能做了負(fù)載均衡(即也有多個服務(wù)端)。
要實(shí)現(xiàn)的目標(biāo)和功能:保證商品不會出現(xiàn)超賣的情況。超賣商品后,無法對商品進(jìn)行發(fā)貨,是一種不負(fù)責(zé)任的行為。
方案實(shí)現(xiàn)討論流程
“要實(shí)現(xiàn)不超賣,首先商品庫存的扣減不能使用框架進(jìn)行更新,因?yàn)榭蚣苁窃O(shè)置值,如果在這段時間,又有人購買了,則商品庫存必然會出現(xiàn)問題。要采用手寫SQL方式。并且sql中還要判斷是否大于等于指定的購買量。”
UPDATE `SKU_Info` SET skuNum=skuNum-1000 WHERE id='00293cb7-d8cf-4470-a66d-bb45ca2b130000293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000;“要實(shí)現(xiàn)不超賣,我們可以對方法加上同步鎖,這樣可以解決”。
“方法加上同步鎖后,用戶下單將會出現(xiàn)排隊(duì)的情況,性能有問題。”
“那我們可以實(shí)現(xiàn)對同一商品進(jìn)行加鎖,這樣可以解決購買不同商品不會相互阻塞。如果有包含關(guān)系,也應(yīng)該加鎖。比如A用戶購買商品1和商品2,B用戶購買商品1,因?yàn)樗麄兌加猩唐?,則應(yīng)該加鎖。”
“你這個方案應(yīng)該可以解決問題,采用分布式鎖的方式可以解決,我們可以使用redis來做。”
“是的,確實(shí)可以解決問題,并且多個服務(wù)端也不存在問題了,就這么干。”
“我們可以對訂單中的所有商品的sku值進(jìn)行排序,拼接成一個skuId值,然后MD5的值作為key,其它訂單進(jìn)來方法時,按同樣的操作進(jìn)行檢測是否正在下單,如果是,則等待。”
“你這種方案忽略了商品不同的情況,就比如上面的例子中,A購買商品1和商品2,B購買商品1,那么他們的key是不同的,因而達(dá)不到效果。”
“我們可以對每個商品sku的id定義個鎖,這樣每次購買時,我們針對每個商品進(jìn)行檢測,這樣就可以了,絕對能夠保證同步。”
“這種方法可行,不過還是存在一個問題,服務(wù)端與redis的連接次數(shù)會比較多,如果一個用戶下單商品種類較多,那么仍然會比較慢,但這確實(shí)不失為一個好的方案。”
“既然這個方案仍然有可能有問題,那么還有沒有其它的方案。”
“數(shù)據(jù)庫本身是有鎖的,可以實(shí)現(xiàn)鎖同步的問題,那么有沒有辦法使用到數(shù)據(jù)庫的鎖來解決這個問題?”
“對呀,我們可以寫SQL語句去循環(huán)扣減庫存,最后判斷數(shù)據(jù)庫影響行數(shù)與商品種類是否匹配?如果不匹配,則是扣減失敗,進(jìn)行還原,如果匹配,則扣減成功!”
“經(jīng)過測試,我們用的MySQL不支持這種方案,里面需要用到if判斷,而if判斷必須要在存儲過程中才能使用。”
“那我們可以使用存儲過程來做。代碼如下”
DELIMITER $$ USE anke_skucenter$$ CREATE PROCEDURE minusSkuNum() BEGIN SET AUTOCOMMIT=0; START TRANSACTION; UPDATE SKU_Info SET skuNum=skuNum-100 WHERE id='0031394c-8058-49f5-9ba9-f971480ac2f2' AND skuNum>=100;IF(SELECT ROW_COUNT()<=0)THENROLLBACK;END IF; UPDATE `SKU_Info` SET skuNum=skuNum-1000 WHERE id='00293cb7-d8cf-4470-a66d-bb45ca2b130000293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000; IF(SELECT ROW_COUNT()<=0)THENROLLBACK; END IF; COMMIT; SET AUTOCOMMIT=1; END$$?“這個是初步的存儲過程,仍然需要將update語句變更為循環(huán),改變傳入?yún)?shù)為商品id和數(shù)量,有誰會寫?”
“額,目前大家都不會寫,并且這個循環(huán)看上去也挺復(fù)雜的。”
“那么我們能不能在mybatis中獲取多條更新語句的影響行數(shù)?”
“不能,沒有任何框架支持,并且mysql本身就不支持,要不然也不會需要存儲過程了。”
“既然多條SQL不行,能不能放到一條SQL中去做更新呢?”
“先baidu下”
“哈哈,找到了,我們查詢的時候有時候回用到case when,那么我們更新的時候是否可以使用這個呢?嘗試代碼如下:”
update SKU_Info set skuNum=skuNum-(case when id='0031394c-8058-49f5-9ba9-f971480ac2f2' then 100 when id='00293cb7-d8cf-4470-a66d-bb45ca2b1300' then 1000 end) where (id='0031394c-8058-49f5-9ba9-f971480ac2f2' AND skuNum>=100) or (id='00293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000);?“經(jīng)過測試,該段代碼執(zhí)行正常,并且能夠正常返回需要的影響行數(shù)。”
“這個循環(huán)的SQL編寫在mybatis中不難,那么判斷最后扣減結(jié)果與商品種類不同時,如何進(jìn)行補(bǔ)償呢?”
“這個可以使用@Transactional,我們在方法上加此注解,在方法內(nèi)部判斷,如果不同,我們就拋出一個自定義異常,這樣就會自動進(jìn)行回滾了。”
“測試一下”
“經(jīng)過幾輪測試,確實(shí)可行,就這樣做。”
“具體實(shí)施為:先生成訂單,然后進(jìn)行扣減,如果捕獲到扣減失敗的自定義異常,則對生成的訂單執(zhí)行刪除標(biāo)記。但存在一個問題,就是標(biāo)記訂單為刪除狀態(tài)失敗的情況,這個訂單仍然存在,也是超賣了。”
“可以調(diào)整下,改為先進(jìn)行扣減,扣減成功再生成訂單,這樣可以避免此問題。”
“嗯,此方法可以解決超賣問題,可能會存在商品扣減成功,但訂單未生成的情況。”
“這種問題會存在,但比超賣要好很多。”
“嗯嗯”
“嗯嗯”
轉(zhuǎn)載于:https://www.cnblogs.com/maomao999/p/9223721.html
總結(jié)
- 上一篇: webpack入门系列2
- 下一篇: Python 字典的操作