Maison > développement back-end > tutoriel php > Explication détaillée d'exemples de fonctions d'achat urgent et de vente flash sous haute concurrence utilisant PHP et Redis

Explication détaillée d'exemples de fonctions d'achat urgent et de vente flash sous haute concurrence utilisant PHP et Redis

炎欲天舞
Libérer: 2023-03-14 15:24:01
original
1935 Les gens l'ont consulté

Les ventes précipitées et les ventes flash sont des scènes très courantes. Les enquêteurs posent souvent des questions lors des entretiens, telles que la façon dont vous mettez en œuvre les ventes précipitées sur Taobao, etc.

Il est très simple de mettre en œuvre des ventes flash et des ventes flash, mais certains problèmes doivent être résolus, en se concentrant principalement sur deux problèmes :

1 La pression de la forte concurrence sur la base de données

2 Comment résoudre la bonne réduction des stocks (problème de "survente") dans des conditions de concurrence

La première question concerne PHP. C'est très simple. Vous pouvez utiliser la technologie de mise en cache pour soulager la pression sur la base de données, comme Memcache, Redis et d'autres technologies de mise en cache.

La deuxième question est plus compliquée :

Écriture conventionnelle :

Interroger l'inventaire du produit correspondant, vérifier s'il est supérieur à 0, puis effectuez des opérations telles que générer une commande. Cependant, pour juger si l'inventaire est supérieur à 0, il y aura des problèmes en cas de concurrence élevée, entraînant un inventaire négatif

<?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;);
}
Copier après la connexion
Que devons-nous faire si cela se produit ? Examinons plusieurs méthodes d'optimisation :

Plan d'optimisation 1 : Définissez le champ du numéro de champ d'inventaire sur non signé Lorsque l'inventaire est à 0, car le champ ne peut pas être un. nombre négatif, renverra faux

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 }
Copier après la connexion

Plan d'optimisation 2 : Utilisez la transaction MySQL pour verrouiller l'opération Ligne


<?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");
}
Copier après la connexion

Solution d'optimisation 3 : Utiliser le verrouillage exclusif de fichier non bloquant


 <?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);
Copier après la connexion

Plan d'optimisation 4 : Utilisez la file d'attente redis, car l'opération pop est atomique, même si de nombreux utilisateurs arrivent en même temps, ils le seront exécuté dans l'ordre. Il est recommandé d'utiliser (Les performances des transactions MySQL diminuent considérablement en cas de concurrence élevée, tout comme la méthode de verrouillage de fichier)

Mettez d'abord l'inventaire du produit dans la file d'attente


 <?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;);
Copier après la connexion
snap up, Description logique


 <?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;);
}
Copier après la connexion
Ce qui précède n'est qu'une simple simulation d'achat précipité dans un contexte de concurrence élevée. est beaucoup plus compliqué que cela, et il y a beaucoup de choses auxquelles il faut prêter attention

comme l'achat urgent. La page est rendue statique et l'interface est appelée via ajax

Encore une fois, ce qui précède. amènera un utilisateur à en saisir plusieurs. L'idée est la suivante :

a besoin d'une file d'attente, d'une file d'attente de résultats instantanés et d'une file d'attente d'inventaire. Dans les situations de concurrence élevée, entrez d'abord l'utilisateur dans la file d'attente, utilisez une boucle de thread pour supprimer un utilisateur de la file d'attente et déterminez si l'utilisateur est déjà dans la file d'attente des résultats d'achat urgent. Si c'est le cas, il a été cassé. vers le haut, sinon il n'est pas récupéré et l'inventaire est réduit de 1. Écrivez

Base de données, placez l'utilisateur dans la file d'attente des résultats.

Lorsque je travaillais sur un projet de centre commercial, j'ai utilisé redis directement pour les ventes flash. Pendant ce temps, j'ai examiné les méthodes ci-dessus, même si elles sont différentes, elles atteignent toutes le même objectif. propre choix et soyez heureux.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal