日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

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

發(fā)布時(shí)間:2023/12/20 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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);????
  • }??
  • ??
  • //模擬下單操作??
  • //庫(kù)存是否大于0??
  • $sql="select?number?from?ih_store?where?goods_id='$goods_id'?and?sku_id='$sku_id'";//解鎖?此時(shí)ih_store數(shù)據(jù)中g(shù)oods_id='$goods_id'?and?sku_id='$sku_id'?的數(shù)據(jù)被鎖住(注3),其它事務(wù)必須等待此次事務(wù)?提交后才能執(zhí)行??
  • $rs=mysql_query($sql,$conn);??
  • $row=mysql_fetch_assoc($rs);??
  • if($row['number']>0){//高并發(fā)下會(huì)導(dǎo)致超賣??
  • ????$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);???
  • ??????
  • ????//庫(kù)存減少??
  • ????$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
  • //庫(kù)存減少??
  • $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;??
  • ??
  • //生成唯一訂單號(hào)??
  • 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);????
  • }??
  • ??
  • //模擬下單操作??
  • //庫(kù)存是否大于0??
  • mysql_query("BEGIN");???//開始事務(wù)??
  • $sql="select?number?from?ih_store?where?goods_id='$goods_id'?and?sku_id='$sku_id'?FOR?UPDATE";//此時(shí)這條記錄被鎖住,其它事務(wù)必須等待此次事務(wù)提交后才能執(zhí)行??
  • $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);???
  • ??????
  • ????//庫(kù)存減少??
  • ????$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");//事務(wù)提交即解鎖??
  • ????}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;??
  • ??
  • //生成唯一訂單號(hào)??
  • 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){//庫(kù)存是否大于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);???
  • ??????
  • ????//庫(kù)存減少??
  • ????$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;??
  • ??
  • //生成唯一訂單號(hào)??
  • 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隊(duì)列庫(kù)存量??
  • $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);???
  • ??
  • //庫(kù)存減少??
  • $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ò),歡迎將生活随笔推薦給好友。