高并發(fā)下用戶搶購(gòu)問(wèn)題簡(jiǎn)答

高并發(fā)下用戶搶購(gòu)問(wèn)題簡(jiǎn)答

前言

面試題當(dāng)中如何處理高并發(fā)用戶搶購(gòu)問(wèn)題可以說(shuō)是一個(gè)十分經(jīng)典的問(wèn)題,經(jīng)常被提及,在這就這個(gè)問(wèn)題寫(xiě)一個(gè)簡(jiǎn)要的解答;

思路

并發(fā)的最大瓶頸永遠(yuǎn)是數(shù)據(jù)庫(kù),MySQL的讀寫(xiě)速度是制約并發(fā)的最大問(wèn)題,而搶購(gòu)之時(shí)真正需要寫(xiě)入的用戶量實(shí)際上是很少的,等于搶購(gòu)的商品總數(shù).這就要求我們需要把無(wú)效的用戶排除出.在前期放入有效用戶量,一旦產(chǎn)品搶購(gòu)結(jié)束,將活動(dòng)頁(yè)改為結(jié)束也的靜態(tài)頁(yè)面,最大程度的提升服務(wù)器相應(yīng)速度.這里面放入限定數(shù)量的用戶是最為關(guān)鍵的地方.
redis的高性能讀寫(xiě)是現(xiàn)在最為主流的解決方案.下面就簡(jiǎn)單的介紹一下如何用redis來(lái)完成高并發(fā)搶購(gòu)處理.

解決方案

將用戶id寫(xiě)入redis列表當(dāng)中,一旦列表長(zhǎng)度達(dá)到商品總數(shù),則拒掉后面的用戶.

示例代碼

Talk is cheap, show you my code.

<?php
/**
 * Created by PhpStorm.
 * User: mc
 * Date: 18/3/21
 * Time: 下午1:53
 */
$user_id = rand(100, 10000); // 模擬用戶id
$key = 'user_list'; // 列表名
$redis = new Redis();
$redis->pconnect('localhost', 6379);
$len = $redis->lLen($key); // 獲取列表長(zhǎng)度
$count = 10; // 商品總數(shù)
if ($len >= $count) { // 達(dá)到商品總數(shù)則停止搶購(gòu)
    echo '秒殺已結(jié)束';
    return '秒殺已結(jié)束';
}
$redis->lPush($key, $user_id);  // 將用戶id推入列表
echo '恭喜你秒殺成功';
return '恭喜你秒殺成功';

ab測(cè)

ab -n 3000 -c 100 http://localhost:8001/     // 模擬3000個(gè)請(qǐng)求,100個(gè)并發(fā)

結(jié)果

列表當(dāng)中的結(jié)果確實(shí)是10條,滿足要求

 LRANGE user_list 0 -1
 1) "466"
 2) "5090"
 3) "8299"
 4) "6436"
 5) "4537"
 6) "9617"
 7) "9291"
 8) "2162"
 9) "1903"
10) "983"
Concurrency Level:      100
Time taken for tests:   4.558 seconds
Complete requests:      3000
Failed requests:        0
Total transferred:      540000 bytes
HTML transferred:       45000 bytes
Requests per second:    658.22 [#/sec] (mean)
Time per request:       151.924 [ms] (mean)
Time per request:       1.519 [ms] (mean, across all concurrent requests)
Transfer rate:          115.70 [Kbytes/sec] received

QPS能夠達(dá)到658.

問(wèn)題

上面的代碼看似沒(méi)問(wèn)題,可是要是老司機(jī)一定不難發(fā)現(xiàn)代碼當(dāng)中存在一個(gè)極大的漏洞.當(dāng)列表當(dāng)中有9個(gè)值,兩個(gè)用戶同時(shí)取得$len,那么這兩個(gè)用戶就會(huì)被同時(shí)寫(xiě)入列表當(dāng)中,這樣就會(huì)出現(xiàn)超賣的問(wèn)題.而且寫(xiě)列表的方式就需要取數(shù)據(jù)要在搶購(gòu)?fù)瓿芍?這顯然不合理.
這需要我們將搶購(gòu)判斷和用戶列表拆分開(kāi)來(lái),redis當(dāng)中的string有一一自增的api具有原子性,哪怕并發(fā)情況下也一定能夠保證自增.這能夠很好的服務(wù)于我們的需求.

代碼示例

$user_id = rand(100, 10000); // 模擬用戶id
$key = 'user_list'; // 列表名
$redis = new Redis();
$redis->connect('localhost', 6379);
$len = $redis->incr('count');
$count = 10; // 商品總數(shù)
if ($len > $count) { // 達(dá)到商品總數(shù)則停止搶購(gòu)
    echo '秒殺已結(jié)束';
    return '秒殺已結(jié)束';
}
$redis->lPush($key, $user_id);  // 將用戶id推入列表
echo '恭喜你秒殺成功';
return '恭喜你秒殺成功';

這樣就可以避免商品超售了,而且列表的生產(chǎn)和消費(fèi)可以同步進(jìn)行,提升業(yè)務(wù)體驗(yàn).當(dāng)然真正的業(yè)務(wù)當(dāng)中復(fù)雜度遠(yuǎn)高于這里.

關(guān)于避免用戶重復(fù)搶的簡(jiǎn)略方案

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$user_id = rand(1, 15);
if (!$redis->hSetNx('seckill', 'test:' . $user_id, 1)) { // hSetNx函數(shù)當(dāng)key存在時(shí)會(huì)返回false,不存在時(shí)才會(huì)被設(shè)置成功,返回true
    echo '您已經(jīng)參加過(guò)秒殺請(qǐng)勿重復(fù)參加';
    return '您已經(jīng)參加過(guò)秒殺請(qǐng)勿重復(fù)參加';
}

$len = $redis->incr('count');
$count = 10; // 商品總數(shù)
if ($len > $count) { // 達(dá)到商品總數(shù)則停止搶購(gòu)
    echo '秒殺已結(jié)束';
    return '秒殺已結(jié)束';
}
$redis->lPush('user_id', $user_id);
echo '恭喜您,秒殺成功';
return '恭喜您,秒殺成功';

上面的QPS才600+而原生的PHP在我的電腦下應(yīng)該能到達(dá)3000+,這里面new Redis()創(chuàng)建redis連接耗費(fèi)了過(guò)多的資源

連接復(fù)用

<?php
$redis = new Redis();
$redis->pconnect('127.0.0.1', 6379); // redis自帶連接復(fù)用函數(shù)
$user_id = rand(1, 15);
if (!$redis->hSetNx('seckill', 'test:' . $user_id, 1)) { // hSetNx函數(shù)當(dāng)key存在時(shí)會(huì)返回false,不存在時(shí)才會(huì)被設(shè)置成功,返回true
    echo '您已經(jīng)參加過(guò)秒殺請(qǐng)勿重復(fù)參加';
    return '您已經(jīng)參加過(guò)秒殺請(qǐng)勿重復(fù)參加';
}

$len = $redis->incr('count');
$count = 10; // 商品總數(shù)
if ($len > $count) { // 達(dá)到商品總數(shù)則停止搶購(gòu)
    echo '秒殺已結(jié)束';
    return '秒殺已結(jié)束';
}
$redis->lPush('user_id', $user_id);
echo '恭喜您,秒殺成功';
return '恭喜您,秒殺成功';
Concurrency Level:      100
Time taken for tests:   0.428 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      180000 bytes
HTML transferred:       15000 bytes
Requests per second:    2337.77 [#/sec] (mean)
Time per request:       42.776 [ms] (mean)
Time per request:       0.428 [ms] (mean, across all concurrent requests)
Transfer rate:          410.94 [Kbytes/sec] received

結(jié)束

QPS能夠高達(dá)2300+完全能夠適應(yīng)一般業(yè)務(wù)的并發(fā)需求了.

最后編輯于
?著作權(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)容

  • 三年前當(dāng)你們坐在夏衍中學(xué)的教室里時(shí),你覺(jué)得三年的高中生涯好漫長(zhǎng),但一轉(zhuǎn)眼,你今天卻以畢業(yè)生的身份坐在了這里。三年光...
    呆小五閱讀 152評(píng)論 0 0
  • 直到今天,2015年秋天的一幕我還是記得那么清楚,那年的涼風(fēng)與陽(yáng)光,落葉與花香;那年我決定了重新留頭發(fā);還有那年我...
    寒煙西斜閱讀 407評(píng)論 9 8
  • (本文微小劇透) 上映多天的《非凡任務(wù)》口碑越來(lái)越趨于兩極化,一端說(shuō)該片是精彩的、弘揚(yáng)了正能量、人物有情有義的好片...
    嗨右閱讀 1,355評(píng)論 1 1
  • 該著色器提供類似卡通效果的渲染結(jié)果??ㄍㄐЧ矬w的特點(diǎn)是,其表面經(jīng)常只使用包含兩個(gè)層次的亮度同一種顏色。尺寸值可以...
    BLENDER閱讀 2,540評(píng)論 0 8

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