php秒殺搶購實現
阿新 • • 發佈:2022-02-11
搶購、秒殺是平常很常見的場景,面試的時候面試官也經常會問到,比如問你淘寶中的搶購秒殺是怎麼實現的等等。
搶購、秒殺實現很簡單,但是有些問題需要解決,主要針對兩個問題:
一、高併發對資料庫產生的壓力
二、競爭狀態下如何解決庫存的正確減少("超賣"問題)
第一個問題,對於PHP來說很簡單,用快取技術就可以緩解資料庫壓力,比如memcache,redis等快取技術。
第二個問題就比較複雜點:
常規寫法:
查詢出對應商品的庫存,看是否大於0,然後執行生成訂單等操作,但是在判斷庫存是否大於0處,如果在高併發下就會有問題,導致庫存量出現負數。
- $conn=mysql_connect("localhost","big","123456");
- if(!$conn){
- echo"connectfailed";
- exit;
- }
- mysql_select_db("big",$conn);
- mysql_query("setnamesutf8");
- $price=10;
- $user_id=1;
- $goods_id=1;
- $sku_id=11;
- $number=1;
- //生成唯一訂單
- functionbuild_order_no(){
- returndate('ymd').substr(implode(NULL,array_map('ord',str_split(substr(uniqid(),7,13),1))),0,8);
- }
- //記錄日誌
- functioninsertLog($event,$type=0){
- global$conn;
- $sql="insertintoih_log(event,type)
- values('$event','$type')";
- mysql_query($sql,$conn);
- }
- //模擬下單操作
- //庫存是否大於0
- $sql="selectnumberfromih_storewheregoods_id='$goods_id'andsku_id='$sku_id'";
- //解鎖此時ih_store資料中goods_id='$goods_id'andsku_id='$sku_id'的資料被鎖住(注3),其它事務必須等待此次事務提交後才能執行
- $rs=mysql_query($sql,$conn);
- $row=mysql_fetch_assoc($rs);
- if($row['number']>0){//高併發下會導致超賣
- $order_sn=build_order_no();
- //生成訂單
- $sql="insertintoih_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="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";
- $store_rs=mysql_query($sql,$conn);
- if(mysql_affected_rows()){
- insertLog('庫存減少成功');
- }else{
- insertLog('庫存減少失敗');
- }
- }else{
- insertLog('庫存不夠');
- }
- 1//庫存減少
- 2$sql="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'andnumber>0";
- 3$store_rs=mysql_query($sql,$conn);
- 4if(mysql_affected_rows()){
- 5insertLog('庫存減少成功');6}
- $conn=mysql_connect("localhost","big","123456");
- if(!$conn){
- echo"connectfailed";
- exit;
- }
- mysql_select_db("big",$conn);
- mysql_query("setnamesutf8");
- $price=10;
- $user_id=1;
- $goods_id=1;
- $sku_id=11;
- $number=1;
- //生成唯一訂單號
- functionbuild_order_no(){
- returndate('ymd').substr(implode(NULL,array_map('ord',str_split(substr(uniqid(),7,13),1))),0,8);
- }
- //記錄日誌
- functioninsertLog($event,$type=0){
- global$conn;
- $sql="insertintoih_log(event,type)
- values('$event','$type')";
- mysql_query($sql,$conn);
- }
- //模擬下單操作
- //庫存是否大於0
- mysql_query("BEGIN");//開始事務
- $sql="selectnumberfromih_storewheregoods_id='$goods_id'andsku_id='$sku_id'FORUPDATE";//此時這條記錄被鎖住,其它事務必須等待此次事務提交後才能執行
- $rs=mysql_query($sql,$conn);
- $row=mysql_fetch_assoc($rs);
- if($row['number']>0){
- //生成訂單
- $order_sn=build_order_no();
- $sql="insertintoih_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="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";
- $store_rs=mysql_query($sql,$conn);
- if(mysql_affected_rows()){
- insertLog('庫存減少成功');
- mysql_query("COMMIT");//事務提交即解鎖
- }else{
- insertLog('庫存減少失敗');
- }
- }else{
- insertLog('庫存不夠');
- mysql_query("ROLLBACK");
- }
- $conn=mysql_connect("localhost","root","123456");
- if(!$conn){
- echo"connectfailed";
- exit;
- }
- mysql_select_db("big-bak",$conn);
- mysql_query("setnamesutf8");
- $price=10;
- $user_id=1;
- $goods_id=1;
- $sku_id=11;
- $number=1;
- //生成唯一訂單號
- functionbuild_order_no(){
- returndate('ymd').substr(implode(NULL,array_map('ord',str_split(substr(uniqid(),7,13),1))),0,8);
- }
- //記錄日誌
- functioninsertLog($event,$type=0){
- global$conn;
- $sql="insertintoih_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="selectnumberfromih_storewheregoods_id='$goods_id'andsku_id='$sku_id'";
- $rs=mysql_query($sql,$conn);
- $row=mysql_fetch_assoc($rs);
- if($row['number']>0){//庫存是否大於0
- //模擬下單操作
- $order_sn=build_order_no();
- $sql="insertintoih_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="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";
- $store_rs=mysql_query($sql,$conn);
- if(mysql_affected_rows()){
- insertLog('庫存減少成功');
- flock($fp,LOCK_UN);//釋放鎖
- }else{
- insertLog('庫存減少失敗');
- }
- }else{
- insertLog('庫存不夠');
- }
- fclose($fp);
- $store=1000;
- $redis=newRedis();
- $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');
- $conn=mysql_connect("localhost","big","123456");
- if(!$conn){
- echo"connectfailed";
- exit;
- }
- mysql_select_db("big",$conn);
- mysql_query("setnamesutf8");
- $price=10;
- $user_id=1;
- $goods_id=1;
- $sku_id=11;
- $number=1;
- //生成唯一訂單號
- functionbuild_order_no(){
- returndate('ymd').substr(implode(NULL,array_map('ord',str_split(substr(uniqid(),7,13),1))),0,8);
- }
- //記錄日誌
- functioninsertLog($event,$type=0){
- global$conn;
- $sql="insertintoih_log(event,type)
- values('$event','$type')";
- mysql_query($sql,$conn);
- }
- //模擬下單操作
- //下單前判斷redis佇列庫存量
- $redis=newRedis();
- $result=$redis->connect('127.0.0.1',6379);
- $count=$redis->lpop('goods_store');
- if(!$count){
- insertLog('error:nostoreredis');
- return;
- }
- //生成訂單
- $order_sn=build_order_no();
- $sql="insertintoih_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="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";
- $store_rs=mysql_query($sql,$conn);
- if(mysql_affected_rows()){
- insertLog('庫存減少成功');
- }else{
- insertLog('庫存減少失敗');
- }