Redis搶購(gòu)設(shè)計(jì)章

搶購(gòu)是如今很常見(jiàn)的一個(gè)應(yīng)用場(chǎng)景,主要需要解決的問(wèn)題有兩個(gè):

1 高并發(fā)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生的壓力

2 競(jìng)爭(zhēng)狀態(tài)下如何解決庫(kù)存的正確減少(“超賣(mài)”問(wèn)題)

對(duì)于第一個(gè)問(wèn)題,已經(jīng)很容易想到用緩存來(lái)處理?yè)屬?gòu),避免直接操作數(shù)據(jù)庫(kù),例如使用Redis。重點(diǎn)在于第二個(gè)問(wèn)題,我們看看下面一種常規(guī)的實(shí)現(xiàn)代碼:


require('predis/src/Autoloader.php');

$redis = new Predis\Client(array(

'scheme' => 'tcp',

'host'? => '127.0.0.1',

'port'? => '6379'

));

//redis 登錄

$redis->auth('123456');

//庫(kù)存

$num = 10;

//用戶id

$user_id = $_SESSION['user_id'];

//檢查庫(kù)存

$len = $redis->llen('order:1');

if($len >= $num){

exit('已經(jīng)搶光了');

}

//把搶到的用戶存入到列表中

$result = $redis->lpush('order:1',$user_id);

if($result){

echo '搶到了';

}

?>

如果代碼正常運(yùn)行,列表order:1中最多只能存儲(chǔ)10個(gè)用戶的id,因?yàn)閹?kù)存只有10個(gè)。

然而,在使用Apache AB工具模擬很多用戶并發(fā)請(qǐng)求時(shí),最后發(fā)現(xiàn)order:1中總是超過(guò)10個(gè)用戶,也就是出現(xiàn)了“超賣(mài)”。

問(wèn)題就出在這一段代碼:

//檢查庫(kù)存

$len = $redis->llen('order:1');

if($len >= $num){

exit('已經(jīng)搶光了');

}

在搶購(gòu)進(jìn)行到一定程度,假如現(xiàn)在已經(jīng)有9個(gè)人搶購(gòu)成功,又來(lái)了3個(gè)用戶同時(shí)搶購(gòu),這時(shí)if條件將會(huì)被繞過(guò),這三個(gè)用戶都能搶購(gòu)成功。而實(shí)際上只有一件庫(kù)存可以搶了。

在高并發(fā)下,很多不是問(wèn)題的,都成了問(wèn)題。要解決“超賣(mài)”問(wèn)題,核心在于保證檢查庫(kù)存時(shí)的操作是依次執(zhí)行的,形象的說(shuō)就是把“多線程”轉(zhuǎn)成“單線程”。即使有很多用戶同時(shí)到達(dá),也是一個(gè)個(gè)檢查并給與搶購(gòu)資格,一旦庫(kù)存搶盡,后面的用戶就無(wú)法繼續(xù)了。

我們需要使用Redis的原子操作來(lái)實(shí)現(xiàn)這個(gè)“單線程”。首先我們把庫(kù)存存在goods:1這個(gè)列表中,假設(shè)有10件庫(kù)存,就往列表中push10個(gè)數(shù),這個(gè)數(shù)沒(méi)有實(shí)際意義,僅僅代表一件庫(kù)存。搶購(gòu)開(kāi)始后,每到來(lái)一個(gè)用戶,就從goods:1中pop一個(gè)數(shù),表示用戶搶購(gòu)成功。當(dāng)列表為空時(shí),表示已經(jīng)被搶光了。因?yàn)榱斜淼膒op操作是原子的,即使有很多用戶同時(shí)到達(dá),也是依次執(zhí)行的。搶購(gòu)的示例代碼如下:


//搶購(gòu)

require('predis/src/Autoloader.php');

$redis = new Predis\Client(array(

'scheme' => 'tcp',

'host'? => '127.0.0.1',

'port'? => '6379'

));

$redis->auth('123456');

//用戶ID

$user_id = $_SESSION['user_id'];

$check = $redis->lpop('goods:1');

if(!$check){

exit('搶光了');

}

$result = $redis->lpush('order:1',$user_id);

if($result){

echo '搶購(gòu)成功';

}

?>


用戶搶購(gòu)成功后,我們將用戶ID存入了order:1列表中。接下來(lái)我們可以引導(dǎo)這些用戶去完成訂單的其他步驟,這里才涉及到與數(shù)據(jù)庫(kù)的交互。最終只有很少的人走到這一步,也就解決的數(shù)據(jù)庫(kù)的壓力問(wèn)題。

為了檢測(cè)實(shí)際效果,我使用Apache AB工具模擬10、20、1000個(gè)用戶并發(fā)進(jìn)行搶購(gòu),經(jīng)過(guò)大量的測(cè)試,最終搶購(gòu)成功的用戶始終為10,沒(méi)有出現(xiàn)“超賣(mài)”。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容