搶購(gòu)是如今很常見的一個(gè)應(yīng)用場(chǎng)景带迟,主要需要解決的問題有兩個(gè):
1 高并發(fā)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生的壓力
2 競(jìng)爭(zhēng)狀態(tài)下如何解決庫(kù)存的正確減少(“超賣”問題)
對(duì)于第一個(gè)問題夕春,已經(jīng)很容易想到用緩存來處理?yè)屬?gòu)飘诗,避免直接操作數(shù)據(jù)庫(kù)虱肄,例如使用Redis谜叹。重點(diǎn)在于第二個(gè)問題,我們看看下面一種常規(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中總是超過10個(gè)用戶,也就是出現(xià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)成功恼策,又來了3個(gè)用戶同時(shí)搶購(gòu)鸦致,這時(shí)if條件將會(huì)被繞過,這三個(gè)用戶都能搶購(gòu)成功涣楷。而實(shí)際上只有一件庫(kù)存可以搶了分唾。
在高并發(fā)下,很多不是問題的狮斗,都成了問題绽乔。要解決“超賣”問題,核心在于保證檢查庫(kù)存時(shí)的操作是依次執(zhí)行的碳褒,形象的說就是把“多線程”轉(zhuǎn)成“單線程”折砸。即使有很多用戶同時(shí)到達(dá),也是一個(gè)個(gè)檢查并給與搶購(gòu)資格沙峻,一旦庫(kù)存搶盡睦授,后面的用戶就無法繼續(xù)了。
我們需要使用Redis的原子操作來實(shí)現(xiàn)這個(gè)“單線程”摔寨。首先我們把庫(kù)存存在goods:1這個(gè)列表中去枷,假設(shè)有10件庫(kù)存,就往列表中push10個(gè)數(shù)是复,這個(gè)數(shù)沒有實(shí)際意義删顶,僅僅代表一件庫(kù)存。搶購(gòu)開始后淑廊,每到來一個(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列表中输虱。接下來我們可以引導(dǎo)這些用戶去完成訂單的其他步驟些楣,這里才涉及到與數(shù)據(jù)庫(kù)的交互。最終只有很少的人走到這一步宪睹,也就解決的數(shù)據(jù)庫(kù)的壓力問題愁茁。
為了檢測(cè)實(shí)際效果,我使用Apache AB工具模擬10亭病、20鹅很、1000個(gè)用戶并發(fā)進(jìn)行搶購(gòu),經(jīng)過大量的測(cè)試罪帖,最終搶購(gòu)成功的用戶始終為10促煮,沒有出現(xiàn)“超賣”。