Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Erläuterung von Beispielen für Rush-Buying- und Flash-Sale-Funktionen bei hoher Parallelität mit PHP und Redis

Detaillierte Erläuterung von Beispielen für Rush-Buying- und Flash-Sale-Funktionen bei hoher Parallelität mit PHP und Redis

炎欲天舞
炎欲天舞Original
2017-08-03 14:56:371841Durchsuche

Eilverkäufe und Flash-Verkäufe sind sehr häufige Szenen in Vorstellungsgesprächen, etwa wie Sie Eilverkäufe auf Taobao umsetzen und so weiter.

Es ist sehr einfach, Flash-Verkäufe und Flash-Verkäufe zu implementieren, aber einige Probleme müssen gelöst werden, wobei der Schwerpunkt hauptsächlich auf zwei Problemen liegt:

1 Der Druck hoher Parallelität auf der Datenbank

2 So lösen Sie die richtige Bestandsreduzierung („Oversold“-Problem) unter Wettbewerbsbedingungen

Die erste Frage ist für PHP. Es ist sehr einfach, Caching-Technologien wie Memcache, Redis und andere Caching-Technologien zu verwenden, um den Datenbankdruck zu verringern.

Die zweite Frage ist komplizierter:

Konventionelles Schreiben:

Fragen Sie den entsprechenden Produktbestand ab und überprüfen Sie ihn ob er größer als 0 ist, und dann Vorgänge wie das Generieren einer Bestellung ausführen. Bei der Beurteilung, ob der Lagerbestand größer als 0 ist, treten jedoch Probleme bei hoher Parallelität auf, was zu einem negativen Lagerbestand führt

<?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;);
}

Was sollen wir tun, wenn das passiert? Schauen wir uns verschiedene Optimierungsmethoden an:

Optimierungsplan 1: Setzen Sie das Feld „Inventarfeldnummer“ auf „Unsigned“, da das Feld nicht a sein kann negative Zahl, gibt false zurück

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 }

Optimierungsplan 2: Verwenden Sie die MySQL-Transaktion, um den Vorgang zu sperren Zeile


<?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");
}

Optimierungslösung 3: Nicht blockierende Datei-Exklusivsperre verwenden


 <?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);

Optimierungsplan 4: Verwenden Sie die Redis-Warteschlange, da der Pop-Vorgang atomar ist, auch wenn viele Benutzer gleichzeitig ankommen Es wird empfohlen, (die Leistung von MySQL-Transaktionen sinkt bei hoher Parallelität erheblich, ebenso wie die Dateisperrmethode)

Stellen Sie zuerst den Produktbestand in die Warteschlange


 <?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;);

Schnappschuss, Beschreibungslogik


 f8e11ea81ba6c588bf1f2299cbda61c6connect('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('库存减少失败');
}

Das Obige ist nur eine einfache Simulation eines Eilkaufs unter hoher Parallelität. Das reale Szenario ist viel komplizierter und es gibt viele Dinge, auf die man achten muss

wie Eilkäufe Die Seite wird statisch gemacht und die Schnittstelle wird über Ajax aufgerufen

Nochmals das oben Gesagte Die Idee ist:

benötigt eine Warteschlange, eine Snap-up-Ergebniswarteschlange und eine Inventarwarteschlange. Geben Sie in Situationen mit hoher Parallelität den Benutzer zunächst in die Warteschlange ein, verwenden Sie eine Thread-Schleife, um einen Benutzer aus der Warteschlange zu entfernen, und stellen Sie fest, ob sich der Benutzer bereits in der Warteschlange für Eilkaufergebnisse befindet. Wenn dies der Fall ist, wurde er geschnappt andernfalls wird es nicht aufgekauft und der Bestand wird um 1 reduziert. Schreiben Sie Datenbank und stellen Sie den Benutzer in die Ergebniswarteschlange.

Als ich an einem Einkaufszentrum-Projekt arbeitete, habe ich Redis direkt für Flash-Verkäufe verwendet. Obwohl sie unterschiedlich sind, erreichen sie alle den gleichen Zweck eigene Wahl und glücklich sein.

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung von Beispielen für Rush-Buying- und Flash-Sale-Funktionen bei hoher Parallelität mit PHP und Redis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn