生活随笔
收集整理的這篇文章主要介紹了
php结合redis实现高并发下的抢购、秒杀功能
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
搶購(gòu)、秒殺是如今很常見的一個(gè)應(yīng)用場(chǎng)景,主要需要解決的問(wèn)題有兩個(gè):
1 高并發(fā)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生的壓力
2 競(jìng)爭(zhēng)狀態(tài)下如何解決庫(kù)存的正確減少("超賣"問(wèn)題)
對(duì)于第一個(gè)問(wèn)題,已經(jīng)很容易想到用緩存來(lái)處理?yè)屬?gòu),避免直接操作數(shù)據(jù)庫(kù),例如使用Redis。
重點(diǎn)在于第二個(gè)問(wèn)題
常規(guī)寫法:
查詢出對(duì)應(yīng)商品的庫(kù)存,看是否大于0,然后執(zhí)行生成訂單等操作,但是在判斷庫(kù)存是否大于0處,如果在高并發(fā)下就會(huì)有問(wèn)題,導(dǎo)致庫(kù)存量出現(xiàn)負(fù)數(shù)
?
[php]?view plaincopy
<?php??$conn=mysql_connect("localhost","big","123456");????if(!$conn){????????echo?"connect?failed";????????exit;????}???mysql_select_db("big",$conn);???mysql_query("set?names?utf8");????$price=10;??$user_id=1;??$goods_id=1;??$sku_id=11;??$number=1;????function?build_order_no(){??????return?date('ymd').substr(implode(NULL,?array_map('ord',?str_split(substr(uniqid(),?7,?13),?1))),?0,?8);??}??function?insertLog($event,$type=0){??????global?$conn;??????$sql="insert?into?ih_log(event,type)???????values('$event','$type')";????????mysql_query($sql,$conn);????}????$sql="select?number?from?ih_store?where?goods_id='$goods_id'?and?sku_id='$sku_id'";$rs=mysql_query($sql,$conn);??$row=mysql_fetch_assoc($rs);??if($row['number']>0){????$order_sn=build_order_no();??????????$sql="insert?into?ih_order(order_sn,user_id,goods_id,sku_id,price)???????values('$order_sn','$user_id','$goods_id','$sku_id','$price')";????????$order_rs=mysql_query($sql,$conn);?????????????????$sql="update?ih_store?set?number=number-{$number}?where?sku_id='$sku_id'";??????$store_rs=mysql_query($sql,$conn);????????if(mysql_affected_rows()){????????????insertLog('庫(kù)存減少成功');??????}else{????????????insertLog('庫(kù)存減少失敗');??????}???}else{??????insertLog('庫(kù)存不夠');??}???>?? ?
優(yōu)化方案1:將庫(kù)存字段number字段設(shè)為unsigned,當(dāng)庫(kù)存為0時(shí),因?yàn)樽侄尾荒転樨?fù)數(shù),將會(huì)返回false
?
[php]?view plaincopy
$sql="update?ih_store?set?number=number-{$number}?where?sku_id='$sku_id'?and?number>0";??$store_rs=mysql_query($sql,$conn);????if(mysql_affected_rows()){????????insertLog('庫(kù)存減少成功');??}?? ?
?
優(yōu)化方案2:使用MySQL的事務(wù),鎖住操作的行
?
[php]?view plaincopy
<?php??$conn=mysql_connect("localhost","big","123456");????if(!$conn){????????echo?"connect?failed";????????exit;????}???mysql_select_db("big",$conn);???mysql_query("set?names?utf8");????$price=10;??$user_id=1;??$goods_id=1;??$sku_id=11;??$number=1;????function?build_order_no(){??????return?date('ymd').substr(implode(NULL,?array_map('ord',?str_split(substr(uniqid(),?7,?13),?1))),?0,?8);??}??function?insertLog($event,$type=0){??????global?$conn;??????$sql="insert?into?ih_log(event,type)???????values('$event','$type')";????????mysql_query($sql,$conn);????}????mysql_query("BEGIN");???$sql="select?number?from?ih_store?where?goods_id='$goods_id'?and?sku_id='$sku_id'?FOR?UPDATE";$rs=mysql_query($sql,$conn);??$row=mysql_fetch_assoc($rs);??if($row['number']>0){??????????$order_sn=build_order_no();???????$sql="insert?into?ih_order(order_sn,user_id,goods_id,sku_id,price)???????values('$order_sn','$user_id','$goods_id','$sku_id','$price')";????????$order_rs=mysql_query($sql,$conn);?????????????????$sql="update?ih_store?set?number=number-{$number}?where?sku_id='$sku_id'";??????$store_rs=mysql_query($sql,$conn);????????if(mysql_affected_rows()){????????????insertLog('庫(kù)存減少成功');??????????mysql_query("COMMIT");????}else{????????????insertLog('庫(kù)存減少失敗');??????}??}else{??????insertLog('庫(kù)存不夠');??????mysql_query("ROLLBACK");??}???>?? ?
優(yōu)化方案3:使用非阻塞的文件排他鎖
?
[php]?view plaincopy
<?php??$conn=mysql_connect("localhost","root","123456");????if(!$conn){????????echo?"connect?failed";????????exit;????}???mysql_select_db("big-bak",$conn);???mysql_query("set?names?utf8");????$price=10;??$user_id=1;??$goods_id=1;??$sku_id=11;??$number=1;????function?build_order_no(){??????return?date('ymd').substr(implode(NULL,?array_map('ord',?str_split(substr(uniqid(),?7,?13),?1))),?0,?8);??}??function?insertLog($event,$type=0){??????global?$conn;??????$sql="insert?into?ih_log(event,type)???????values('$event','$type')";????????mysql_query($sql,$conn);????}????$fp?=?fopen("lock.txt",?"w+");??if(!flock($fp,LOCK_EX?|?LOCK_NB)){??????echo?"系統(tǒng)繁忙,請(qǐng)稍后再試";??????return;??}??$sql="select?number?from?ih_store?where?goods_id='$goods_id'?and?sku_id='$sku_id'";??$rs=mysql_query($sql,$conn);??$row=mysql_fetch_assoc($rs);??if($row['number']>0){????????$order_sn=build_order_no();???????$sql="insert?into?ih_order(order_sn,user_id,goods_id,sku_id,price)???????values('$order_sn','$user_id','$goods_id','$sku_id','$price')";????????$order_rs=mysql_query($sql,$conn);?????????????????$sql="update?ih_store?set?number=number-{$number}?where?sku_id='$sku_id'";??????$store_rs=mysql_query($sql,$conn);????????if(mysql_affected_rows()){????????????insertLog('庫(kù)存減少成功');??????????flock($fp,LOCK_UN);????}else{????????????insertLog('庫(kù)存減少失敗');??????}???}else{??????insertLog('庫(kù)存不夠');??}??fclose($fp);??
?
優(yōu)化方案4:使用redis隊(duì)列,因?yàn)閜op操作是原子的,即使有很多用戶同時(shí)到達(dá),也是依次執(zhí)行,推薦使用(mysql事務(wù)在高并發(fā)下性能下降很厲害,文件鎖的方式也是)
?
先將商品庫(kù)存如隊(duì)列
?
[php]?view plaincopy
<?php??$store=1000;??$redis=new?Redis();??$result=$redis->connect('127.0.0.1',6379);??$res=$redis->llen('goods_store');??echo?$res;??$count=$store-$res;??for($i=0;$i<$count;$i++){??????$redis->lpush('goods_store',1);??}??echo?$redis->llen('goods_store');???>??
搶購(gòu)、描述邏輯
?
?
[php]?view plaincopy
<?php??$conn=mysql_connect("localhost","big","123456");????if(!$conn){????????echo?"connect?failed";????????exit;????}???mysql_select_db("big",$conn);???mysql_query("set?names?utf8");????$price=10;??$user_id=1;??$goods_id=1;??$sku_id=11;??$number=1;????function?build_order_no(){??????return?date('ymd').substr(implode(NULL,?array_map('ord',?str_split(substr(uniqid(),?7,?13),?1))),?0,?8);??}??function?insertLog($event,$type=0){??????global?$conn;??????$sql="insert?into?ih_log(event,type)???????values('$event','$type')";????????mysql_query($sql,$conn);????}????$redis=new?Redis();??$result=$redis->connect('127.0.0.1',6379);??$count=$redis->lpop('goods_store');??if(!$count){??????insertLog('error:no?store?redis');??????return;??}????$order_sn=build_order_no();??$sql="insert?into?ih_order(order_sn,user_id,goods_id,sku_id,price)???values('$order_sn','$user_id','$goods_id','$sku_id','$price')";????$order_rs=mysql_query($sql,$conn);?????$sql="update?ih_store?set?number=number-{$number}?where?sku_id='$sku_id'";??$store_rs=mysql_query($sql,$conn);????if(mysql_affected_rows()){????????insertLog('庫(kù)存減少成功');??}else{????????insertLog('庫(kù)存減少失敗');??}???
模擬5000高并發(fā)測(cè)試
webbench -c 5000 -t 60 http://192.168.1.198/big/index.php
ab -r -n 6000 -c 5000 ?http://192.168.1.198/big/index.php
?
?
上述只是簡(jiǎn)單模擬高并發(fā)下的搶購(gòu),真實(shí)場(chǎng)景要比這復(fù)雜很多,很多注意的地方
如搶購(gòu)頁(yè)面做成靜態(tài)的,通過(guò)ajax調(diào)用接口
再如上面的會(huì)導(dǎo)致一個(gè)用戶搶多個(gè),思路:
需要一個(gè)排隊(duì)隊(duì)列和搶購(gòu)結(jié)果隊(duì)列及庫(kù)存隊(duì)列。高并發(fā)情況,先將用戶進(jìn)入排隊(duì)隊(duì)列,用一個(gè)線程循環(huán)處理從排隊(duì)隊(duì)列取出一個(gè)用戶,判斷用戶是否已在搶購(gòu)結(jié)果隊(duì)列,如果在,則已搶購(gòu),否則未搶購(gòu),庫(kù)存減1,寫數(shù)據(jù)庫(kù),將用戶入結(jié)果隊(duì)列。
?
測(cè)試數(shù)據(jù)表
?
[php]?view plaincopy
--??--?數(shù)據(jù)庫(kù):?`big`??--????--?--------------------------------------------------------????--??--?表的結(jié)構(gòu)?`ih_goods`??--??????CREATE?TABLE?IF?NOT?EXISTS?`ih_goods`?(????`goods_id`?int(10)?unsigned?NOT?NULL?AUTO_INCREMENT,????`cat_id`?int(11)?NOT?NULL,????`goods_name`?varchar(255)?NOT?NULL,????PRIMARY?KEY?(`goods_id`)??)?ENGINE=MyISAM??DEFAULT?CHARSET=utf8?AUTO_INCREMENT=2?;??????--??--?轉(zhuǎn)存表中的數(shù)據(jù)?`ih_goods`??--??????INSERT?INTO?`ih_goods`?(`goods_id`,?`cat_id`,?`goods_name`)?VALUES??(1,?0,?'小米手機(jī)');????--?--------------------------------------------------------????--??--?表的結(jié)構(gòu)?`ih_log`??--????CREATE?TABLE?IF?NOT?EXISTS?`ih_log`?(????`id`?int(11)?NOT?NULL?AUTO_INCREMENT,????`event`?varchar(255)?NOT?NULL,????`type`?tinyint(4)?NOT?NULL?DEFAULT?'0',????`addtime`?timestamp?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP,????PRIMARY?KEY?(`id`)??)?ENGINE=MyISAM?DEFAULT?CHARSET=utf8?AUTO_INCREMENT=1?;????--??--?轉(zhuǎn)存表中的數(shù)據(jù)?`ih_log`??--??????--?--------------------------------------------------------????--??--?表的結(jié)構(gòu)?`ih_order`??--????CREATE?TABLE?IF?NOT?EXISTS?`ih_order`?(????`id`?int(11)?NOT?NULL?AUTO_INCREMENT,????`order_sn`?char(32)?NOT?NULL,????`user_id`?int(11)?NOT?NULL,????`status`?int(11)?NOT?NULL?DEFAULT?'0',????`goods_id`?int(11)?NOT?NULL?DEFAULT?'0',????`sku_id`?int(11)?NOT?NULL?DEFAULT?'0',????`price`?float?NOT?NULL,????`addtime`?timestamp?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP,????PRIMARY?KEY?(`id`)??)?ENGINE=InnoDB?DEFAULT?CHARSET=utf8?COMMENT='訂單表'?AUTO_INCREMENT=1?;????--??--?轉(zhuǎn)存表中的數(shù)據(jù)?`ih_order`??--??????--?--------------------------------------------------------????--??--?表的結(jié)構(gòu)?`ih_store`??--????CREATE?TABLE?IF?NOT?EXISTS?`ih_store`?(????`id`?int(11)?NOT?NULL?AUTO_INCREMENT,????`goods_id`?int(11)?NOT?NULL,????`sku_id`?int(10)?unsigned?NOT?NULL?DEFAULT?'0',????`number`?int(10)?NOT?NULL?DEFAULT?'0',????`freez`?int(11)?NOT?NULL?DEFAULT?'0'?COMMENT?'虛擬庫(kù)存',????PRIMARY?KEY?(`id`)??)?ENGINE=InnoDB??DEFAULT?CHARSET=utf8?COMMENT='庫(kù)存'?AUTO_INCREMENT=2?;????--??--?轉(zhuǎn)存表中的數(shù)據(jù)?`ih_store`??--????INSERT?INTO?`ih_store`?(`id`,?`goods_id`,?`sku_id`,?`number`,?`freez`)?VALUES??(1,?1,?11,?500,?0);??
轉(zhuǎn)載于:https://www.cnblogs.com/yzycoder/p/6762022.html
總結(jié)
以上是生活随笔為你收集整理的php结合redis实现高并发下的抢购、秒杀功能的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。