生活随笔
收集整理的這篇文章主要介紹了
php结合redis实现高并发下的抢购、秒杀功能
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
搶購、秒殺是如今很常見的一個應用場景,主要需要解決的問題有兩個:
1 高并發對數據庫產生的壓力
2 競爭狀態下如何解決庫存的正確減少("超賣"問題)
對于第一個問題,已經很容易想到用緩存來處理搶購,避免直接操作數據庫,例如使用Redis。
重點在于第二個問題
常規寫法:
查詢出對應商品的庫存,看是否大于0,然后執行生成訂單等操作,但是在判斷庫存是否大于0處,如果在高并發下就會有問題,導致庫存量出現負數
?
[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('庫存減少成功');??????}else{????????????insertLog('庫存減少失敗');??????}???}else{??????insertLog('庫存不夠');??}???>?? ?
優化方案1:將庫存字段number字段設為unsigned,當庫存為0時,因為字段不能為負數,將會返回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('庫存減少成功');??}?? ?
?
優化方案2:使用MySQL的事務,鎖住操作的行
?
[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('庫存減少成功');??????????mysql_query("COMMIT");????}else{????????????insertLog('庫存減少失敗');??????}??}else{??????insertLog('庫存不夠');??????mysql_query("ROLLBACK");??}???>?? ?
優化方案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?"系統繁忙,請稍后再試";??????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('庫存減少成功');??????????flock($fp,LOCK_UN);????}else{????????????insertLog('庫存減少失敗');??????}???}else{??????insertLog('庫存不夠');??}??fclose($fp);??
?
優化方案4:使用redis隊列,因為pop操作是原子的,即使有很多用戶同時到達,也是依次執行,推薦使用(mysql事務在高并發下性能下降很厲害,文件鎖的方式也是)
?
先將商品庫存如隊列
?
[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');???>??
搶購、描述邏輯
?
?
[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('庫存減少成功');??}else{????????insertLog('庫存減少失敗');??}???
模擬5000高并發測試
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
?
?
上述只是簡單模擬高并發下的搶購,真實場景要比這復雜很多,很多注意的地方
如搶購頁面做成靜態的,通過ajax調用接口
再如上面的會導致一個用戶搶多個,思路:
需要一個排隊隊列和搶購結果隊列及庫存隊列。高并發情況,先將用戶進入排隊隊列,用一個線程循環處理從排隊隊列取出一個用戶,判斷用戶是否已在搶購結果隊列,如果在,則已搶購,否則未搶購,庫存減1,寫數據庫,將用戶入結果隊列。
?
測試數據表
?
[php]?view plaincopy
--??--?數據庫:?`big`??--????--?--------------------------------------------------------????--??--?表的結構?`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?;??????--??--?轉存表中的數據?`ih_goods`??--??????INSERT?INTO?`ih_goods`?(`goods_id`,?`cat_id`,?`goods_name`)?VALUES??(1,?0,?'小米手機');????--?--------------------------------------------------------????--??--?表的結構?`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?;????--??--?轉存表中的數據?`ih_log`??--??????--?--------------------------------------------------------????--??--?表的結構?`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?;????--??--?轉存表中的數據?`ih_order`??--??????--?--------------------------------------------------------????--??--?表的結構?`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?'虛擬庫存',????PRIMARY?KEY?(`id`)??)?ENGINE=InnoDB??DEFAULT?CHARSET=utf8?COMMENT='庫存'?AUTO_INCREMENT=2?;????--??--?轉存表中的數據?`ih_store`??--????INSERT?INTO?`ih_store`?(`id`,?`goods_id`,?`sku_id`,?`number`,?`freez`)?VALUES??(1,?1,?11,?500,?0);??
轉載于:https://www.cnblogs.com/yzycoder/p/6762022.html
總結
以上是生活随笔為你收集整理的php结合redis实现高并发下的抢购、秒杀功能的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。