日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > php >内容正文

php

php结合redis实现高并发下的抢购、秒杀功能

發布時間:2023/12/20 php 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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);????
  • }??
  • ??
  • //模擬下單操作??
  • //庫存是否大于0??
  • $sql="select?number?from?ih_store?where?goods_id='$goods_id'?and?sku_id='$sku_id'";//解鎖?此時ih_store數據中goods_id='$goods_id'?and?sku_id='$sku_id'?的數據被鎖住(注3),其它事務必須等待此次事務?提交后才能執行??
  • $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);????
  • }??
  • ??
  • //模擬下單操作??
  • //庫存是否大于0??
  • 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){//庫存是否大于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隊列庫存量??
  • $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实现高并发下的抢购、秒杀功能的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。