ホームページ > バックエンド開発 > PHPチュートリアル > PHPとredisを使った高同時実行時の駆け込み購入・フラッシュセール機能の例を詳しく解説

PHPとredisを使った高同時実行時の駆け込み購入・フラッシュセール機能の例を詳しく解説

炎欲天舞
リリース: 2023-03-14 15:24:01
オリジナル
1928 人が閲覧しました

スナップアップ セールスとフラッシュ セールスは、面接中に面接官からよく聞かれる場面です。たとえば、タオバオなどでフラッシュ セールスを達成する方法について尋ねられます。

ラッシュ セールとフラッシュ セールの実装は非常に簡単ですが、主に次の 2 つの問題に焦点を当てて、解決する必要がある問題がいくつかあります。

2 競争状態で在庫を正しく削減する方法 (「売られすぎ」問題)

PHP の場合、最初の質問は非常に簡単です。memcache、redis などのキャッシュ テクノロジを使用してデータベースの負荷を軽減できます。キャッシュ技術。

2 番目の質問はより複雑です:

従来の書き方:

対応する製品の在庫をクエリして、それが 0 より大きいかどうかを確認し、注文の生成などの操作を実行しますが、判断する場合は在庫が 0 より大きいかどうか、同時実行性が高い場合に問題が発生して在庫がマイナスになるかどうか

<?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(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type) 
    values(&#39;$event&#39;,&#39;$type&#39;)"; 
    mysql_query($sql,$conn); 
}
 
//模拟下单操作
//库存是否大于0
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39;";
//解锁 此时ih_store数据中goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; 的数据被锁住(注3),其它事务必须等待此次事务 提交后才能执行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row[&#39;number&#39;]>0){//高并发下会导致超卖
    $order_sn=build_order_no();
    //生成订单 
    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
    values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)"; 
    $order_rs=mysql_query($sql,$conn); 
     
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysql_query($sql,$conn); 
    if(mysql_affected_rows()){ 
        insertLog(&#39;库存减少成功&#39;);
    }else{ 
        insertLog(&#39;库存减少失败&#39;);
    } 
}else{
    insertLog(&#39;库存不够&#39;);
}
ログイン後にコピー
これが起こった場合はどうすればよいですか?いくつかの最適化方法を見てみましょう:

最適化計画 1:

在庫フィールド番号フィールドを符号なしに設定します。在庫が 0 の場合、フィールドは負の数値にすることができないため、false が返されます

1 //库存减少
2 $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39; and number>0";
3 $store_rs=mysql_query($sql,$conn); 
4 if(mysql_affected_rows()){ 
5     insertLog(&#39;库存减少成功&#39;);6 }
ログイン後にコピー

。最適化計画 2:

MySQL トランザクションを使用して操作の行をロックします

<?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(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type) 
    values(&#39;$event&#39;,&#39;$type&#39;)"; 
    mysql_query($sql,$conn); 
}
 
//模拟下单操作
//库存是否大于0
mysql_query("BEGIN");   //开始事务
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row[&#39;number&#39;]>0){
    //生成订单 
    $order_sn=build_order_no(); 
    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
    values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)"; 
    $order_rs=mysql_query($sql,$conn); 
     
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysql_query($sql,$conn); 
    if(mysql_affected_rows()){ 
        insertLog(&#39;库存减少成功&#39;);
        mysql_query("COMMIT");//事务提交即解锁
    }else{ 
        insertLog(&#39;库存减少失败&#39;);
    }
}else{
    insertLog(&#39;库存不够&#39;);
    mysql_query("ROLLBACK");
}
ログイン後にコピー
最適化ソリューション 3: ノンブロッキングファイル排他ロックを使用します

 <?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(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type) 
    values(&#39;$event&#39;,&#39;$type&#39;)"; 
    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=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39;";
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row[&#39;number&#39;]>0){//库存是否大于0
    //模拟下单操作 
    $order_sn=build_order_no(); 
    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
    values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)"; 
    $order_rs=mysql_query($sql,$conn); 
     
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysql_query($sql,$conn); 
    if(mysql_affected_rows()){ 
        insertLog(&#39;库存减少成功&#39;);
        flock($fp,LOCK_UN);//释放锁
    }else{ 
        insertLog(&#39;库存减少失败&#39;);
    } 
}else{
    insertLog(&#39;库存不够&#39;);
}
fclose($fp);
ログイン後にコピー

最適化ソリューション4: ポップ操作はアトミックであるため、多くのユーザーが同時に到着した場合でも、順番に実行されるため、使用することをお勧めします (同時実行性が高いと、mysql トランザクションのパフォーマンスが大幅に低下します)。ファイルロック方式) 商品在庫を優先する キューなど


 <?php
$store=1000;
$redis=new Redis();
$result=$redis->connect(&#39;127.0.0.1&#39;,6379);
$res=$redis->llen(&#39;goods_store&#39;);
echo $res;
$count=$store-$res;
for($i=0;$i<$count;$i++){
    $redis->lpush(&#39;goods_store&#39;,1);
}
echo $redis->llen(&#39;goods_store&#39;);
ログイン後にコピー

ラッシュ購入、説明ロジック

 <?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(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type) 
    values(&#39;$event&#39;,&#39;$type&#39;)"; 
    mysql_query($sql,$conn); 
}
 
//模拟下单操作
//下单前判断redis队列库存量
$redis=new Redis();
$result=$redis->connect(&#39;127.0.0.1&#39;,6379);
$count=$redis->lpop(&#39;goods_store&#39;);
if(!$count){
    insertLog(&#39;error:no store redis&#39;);
    return;
}
 
//生成订单 
$order_sn=build_order_no();
$sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)"; 
$order_rs=mysql_query($sql,$conn); 
 
//库存减少
$sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
$store_rs=mysql_query($sql,$conn); 
if(mysql_affected_rows()){ 
    insertLog(&#39;库存减少成功&#39;);
}else{ 
    insertLog(&#39;库存减少失败&#39;);
}
ログイン後にコピー

上記は、高い同時実行性の下でのラッシュ購入の単純なシミュレーションです。これよりも複雑で、注意すべき点がたくさんあります

たとえば、急ぎ購入ページを静的に作成した場合、ajax を介してインターフェイスを呼び出します


繰り返しになりますが、上記のアイデアでは、1 人のユーザーが複数のアイテムを取得することになります。は:

には、キューイング キュー、購入結果キュー、在庫キューが必要です。同時実行性が高い場合は、まずユーザーをキューイング キューに入れ、スレッド ループを使用してユーザーをキューイング キューから削除し、そのユーザーがすでにラッシュ購入結果キューに入っているかどうかを判断します。それ以外の場合、インベントリは 1 減らされ、データベースに書き込まれます。

、ユーザーは結果キューに入れられます。


私がショッピングモールプロジェクトに取り組んでいたとき、私は上記の方法を検討しましたが、それらはすべて同じ目的を達成することができます。嬉しいです。

以上がPHPとredisを使った高同時実行時の駆け込み購入・フラッシュセール機能の例を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート