不使用緩存(redis诺核、memcache),如何設(shè)計(jì)高效搶購(gòu)業(yè)務(wù)呢?
常見的搶購(gòu)業(yè)務(wù)主要有:
商品搶購(gòu)
券搶購(gòu)
紅包搶購(gòu)
今天咱們就談?wù)勅绾螌?duì)這些搶購(gòu)業(yè)務(wù)做統(tǒng)一設(shè)計(jì)祝钢,只使用Mysql做高并發(fā)活動(dòng)。
我們先看下面一張表若厚,分別標(biāo)出了這三類業(yè)務(wù)涉及到的核心數(shù)據(jù)量:
核心數(shù)據(jù)量
數(shù)據(jù)量 | 商品庫(kù)存 | 券庫(kù)存 | 紅包庫(kù)存 | 數(shù)據(jù)金額 |
---|---|---|---|---|
1.總量 | totalNum | totalNum | totalMoney | 總金額 |
2.銷售量 | sellNum | sellNum | sellMoney | 領(lǐng)取金額 |
3.鎖定量 | lockNum | lockNum | 0 | 鎖定金額 |
4.庫(kù)存量 | stockNum | stockNum | stockMoney | 庫(kù)存金額 |
5.退貨量 | refundNum | refundNum | 0 | 0 |
6,庫(kù)存池 | inventory | inventory | - | - |
我們?cè)倏聪逻@些業(yè)務(wù)涉及到的核心節(jié)點(diǎn)的數(shù)據(jù)量變化
核心數(shù)據(jù)量變化(變化量為n)
節(jié)點(diǎn)名稱 | 總量 | 銷售量 | 鎖定量 | 庫(kù)存量 | 退貨量 |
---|---|---|---|---|---|
下單 | totalNum不變 | sellNum不變 | lockNum+n | stockNum不變 | refundNum不變 |
取消訂單 | totalNum不變 | sellNum不變 | lockNum-n | stockNum不變 | refundNum不變 |
支付 | totalNum不變 | sellNum+n | lockNum-n | stockNum-n | refundNum不變 |
核銷 | totalNum不變 | sellNum不變 | lockNum不變 | stockNum不變 | refundNum不變 |
退款 | totalNum不變 | sellNum-n(或不變) | lockNum不變 | stockNum+n(或不變) | refundNum+n |
節(jié)點(diǎn)設(shè)計(jì)
我們知道拦英,每個(gè)操作節(jié)點(diǎn)最少可對(duì)應(yīng)一個(gè)操作。而我們?yōu)榱吮WC接口效率最高测秸,我們需要盡量少的使用sql操作疤估,如果每個(gè)接口只有一次數(shù)據(jù)操作那是最好的灾常。
但是,每個(gè)接口的邏輯可能都會(huì)涉及到若干數(shù)據(jù)業(yè)務(wù)操作铃拇,而我們?yōu)榱四茏屵@些搶購(gòu)業(yè)務(wù)性能盡量好钞瀑,那我們就要盡量把接口原子化設(shè)計(jì)。而在這些接口中慷荔,下單操作時(shí)重中之重雕什,只要把這個(gè)下單接口設(shè)計(jì)好了,后續(xù)流程基本都沒啥大問題显晶。
下單接口設(shè)計(jì)
那我們來分析一下下單接口的設(shè)計(jì)業(yè)務(wù):
檢查庫(kù)存是否夠用
防止并發(fā)超賣
鎖庫(kù)存
等等...
那這些要求改如何同時(shí)滿足呢贷岸?那就要我們盡量合理使用核心數(shù)據(jù)量了!
那我們?cè)谠O(shè)計(jì)庫(kù)存表的時(shí)候吧碾,盡量把總量凰盔、銷售量、鎖定量倦春、庫(kù)存量放到一起户敬,可以如下設(shè)計(jì):
CREATE TABLE `goods_stock` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
`SPU_ID` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品SPU_ID',
`SKU_ID` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品SKU ID',
`STOCK_LEFT_NUM` int(11) NOT NULL DEFAULT '0' COMMENT '剩余庫(kù)存數(shù)量',
`STOCK_SALE_NUM` int(11) NOT NULL DEFAULT '0' COMMENT '售賣庫(kù)存數(shù)量',
`STOCK_LOCK_NUM` int(11) NOT NULL DEFAULT '0' COMMENT '被鎖定的庫(kù)存數(shù)量',
`STOCK_TOTAL_NUM` int(11) NOT NULL DEFAULT '0' COMMENT 'SKU總庫(kù)存量,0表示不限制庫(kù)存',
`STATUS` tinyint(4) NOT NULL DEFAULT '0' COMMENT '本條記錄狀態(tài),0-有效睁本,1-無效',
`CREATE_TIME` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
`UPDATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
`VERSION` int(11) NOT NULL DEFAULT '0' COMMENT '樂觀鎖版本號(hào)',
PRIMARY KEY (`ID`),
KEY `IDX_SPU_ID` (`SPU_ID`),
KEY `IDX_SKU_ID` (`SKU_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品SKU庫(kù)存表';
其他字段在實(shí)例中省略了尿庐。
我們看看如何使用這一張表滿足上述要求:
1,檢查庫(kù)存是否夠用:
STOCK_LEFT_NUM >= n
2呢堰,防止并發(fā)超賣:
利用數(shù)據(jù)庫(kù)行級(jí)鎖防并發(fā)
WHERE SKU_ID = ${sku_id}
3抄瑟,鎖庫(kù)存:
STOCK_LOCK_NUM = STOCK_LOCK_NUM + n
那么,具體的下單枉疼、取消訂單皮假、支付等業(yè)務(wù)節(jié)點(diǎn)就可以如下實(shí)現(xiàn):
1,下單
UPDATE `goods_stock`
SET `STOCK_LOCK_NUM` = `STOCK_LOCK_NUM` + ${lockNum}
WHERE `STOCK_LEFT_NUM` <![CDATA[ >= ]]> ${lockNum}
AND `SKU_ID` = ${skuId}
AND ${lockNum} <![CDATA[ > ]]> 0
當(dāng)該操作執(zhí)行成功骂维,再處理其他邏輯惹资;如果失敗,就直接返回下單失敗航闺。
2褪测,取消訂單
UPDATE `goods_stock`
SET `STOCK_LOCK_NUM` = `STOCK_LOCK_NUM` - ${lockNum}
WHERE `SKU_ID` = ${skuId}
AND ${lockNum} <![CDATA[ > ]]> 0
3,支付訂單
UPDATE `goods_stock`
SET `STOCK_LOCK_NUM` = `STOCK_LOCK_NUM` - ${lockNum},
`STOCK_SALE_NUM` = `STOCK_SALE_NUM` + ${lockNum},
`STOCK_LEFT_NUM` = `STOCK_LEFT_NUM` - ${lockNum}
WHERE `STOCK_LOCK_NUM` <![CDATA[ >= ]]> ${lockNum}
AND `SKU_ID` = ${skuId}
AND ${lockNum} <![CDATA[ > ]]> 0
目前MySql+SD硬盤的話潦刃,MySql事務(wù)并發(fā)量達(dá)到2000是沒啥壓力的侮措,所以一般并發(fā)2000左右的都可以直接使用MySql數(shù)據(jù)庫(kù)設(shè)計(jì)業(yè)務(wù)邏輯,無需使用Redis緩存乖杠。
這里只是給出了商品庫(kù)存核心邏輯分扎,券和其類似,不過還多一點(diǎn)庫(kù)存池(存放券碼)胧洒,其實(shí)這個(gè)可以放到后臺(tái)去處理了畏吓;紅包比這個(gè)更簡(jiǎn)單环揽,只有下單(領(lǐng)紅包)操作。
-End-