Heim >Backend-Entwicklung >PHP-Tutorial >Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt)

Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt)

慕斯
慕斯nach vorne
2021-05-25 15:01:234749Durchsuche

Der letzte Artikel stellte Ihnen vor: „Lassen Sie uns mehr über die if-Anweisung der PHP-Flusskontrollanweisung erfahren!“ ! ! (Quellcode beigefügt)“, in diesem Artikel wird weiterhin PHP zur Lösung von Problemen mit hoher Parallelität vorgestellt

Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt)

Empfehlung für einen Videokurs →: „Lösung für die Parallelität von Daten auf Zehn-Millionen-Ebene (Theorie + Praxis)“

Beispiel An einer Autobahnkreuzung kommen beispielsweise 5 Autos pro Sekunde vorbei und überholen 5 Autos, und die Autobahnkreuzung funktioniert normal. Plötzlich können nur noch 4 Autos diese Kreuzung in einer Sekunde passieren und der Verkehrsfluss ist immer noch derselbe. Daher wird es definitiv einen Stau geben. (Es fühlt sich an, als würden aus 5 Fahrspuren plötzlich 4 Fahrspuren)

In ähnlicher Weise sind in einer bestimmten Sekunde 20 * 500 verfügbare Verbindungsprozesse voll ausgelastet, aber es sind immer noch 10.000 neue Anfragen und keine Verbindungsprozesse verfügbar, so wird erwartet Das System gerät in einen abnormalen Zustand.

Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt)

Tatsächlich treten in normalen Geschäftsszenarien ohne hohe Parallelität auch Probleme mit einer bestimmten Geschäftsanforderungsschnittstelle auf, und die Antwortzeit der gesamten Webanforderung ist extrem langsam ist sehr lang und das Web wird allmählich reduziert. Die Anzahl der verfügbaren Verbindungen auf dem Server ist voll und es ist kein Verbindungsprozess für andere normale Geschäftsanforderungen verfügbar.

Je erschreckender das Problem ist, desto unzugänglicher ist das System, desto häufiger klicken die Benutzer. Der Teufelskreis führt schließlich zu einer „Lawine“ (einer der Web-Rechner hängt sich auf, wodurch der Datenverkehr unterbrochen wird). auf andere normal funktionierende Maschinen verteilt), was dazu führt, dass auch normale Maschinen hängen bleiben und dann ein Teufelskreis entsteht, der das gesamte Web-System zum Absturz bringt.

Neustart- und Überlastschutz

Sollte es zu einer „Lawine“ im System kommen, wird ein vorschneller Neustart des Dienstes das Problem nicht lösen. Das häufigste Phänomen ist, dass es nach dem Start sofort aufhängt. Zu diesem Zeitpunkt ist es am besten, den Datenverkehr auf der Eingangsebene zu verweigern und dann neu zu starten. Wenn auch Dienste wie Redis/Memcache ausfallen, müssen Sie beim Neustart auf das „Aufwärmen“ achten, was lange dauern kann.

In Flash-Sale- und Rush-Sale-Szenarien übersteigt der Datenverkehr oft die Vorbereitung und Vorstellungskraft unseres Systems. Zu diesem Zeitpunkt ist ein Überlastschutz erforderlich. Das Ablehnen von Anfragen ist auch eine Schutzmaßnahme, wenn eine vollständige Systemauslastung festgestellt wird. Das Einrichten der Filterung im Frontend ist der einfachste Weg, aber dieser Ansatz wird von Benutzern „kritisiert“. Angemessener ist es, einen Überlastungsschutz auf der CGI-Eintrittsebene festzulegen, um direkte Anfragen von Kunden schnell zurückzugeben „Thread-Sicherheit“ (mehrere Threads führen denselben Code gleichzeitig aus. Wenn das Ergebnis jedes Laufs mit dem eines Einzel-Thread-Laufs übereinstimmt und das Ergebnis mit den Erwartungen übereinstimmt, ist es Thread-sicher.) . Wenn es sich um eine MySQL-Datenbank handelt, können Sie das Problem mit einem eigenen Sperrmechanismus lösen. In großen Parallelitätsszenarien wird MySQL jedoch nicht empfohlen. Bei Flash-Sale- und Rush-Sale-Szenarien gibt es ein weiteres Problem, nämlich die „Überlieferung“. Wenn dieser Aspekt nicht sorgfältig kontrolliert wird, kommt es zu einer übermäßigen Lieferung. Wir haben auch gehört, dass einige E-Commerce-Unternehmen Eilkäufe durchführen. Nachdem der Käufer das Produkt erfolgreich gekauft hat, erkennt der Händler die Bestellung nicht als gültig an und weigert sich, die Ware zu liefern. Das Problem liegt hier möglicherweise nicht unbedingt darin, dass der Händler betrügerisch vorgeht, sondern darin, dass die Gefahr einer übermäßigen Ausgabe auf der technischen Ebene des Systems besteht.

Der Grund für die übermäßige Ausgabe

    Angenommen, wir haben in einem bestimmten Eilkaufszenario insgesamt nur 100 Produkte, wir haben im letzten Moment 99 Produkte verbraucht und nur noch das letzte. Zu diesem Zeitpunkt sendete das System mehrere gleichzeitige Anfragen. Die von diesen Anfragen gelesenen Produktsalden betrugen alle 99, und dann verabschiedeten sie alle diese Saldenbeurteilung, was schließlich zu einer Überausgabe führte. (Dasselbe wie in der zuvor im Artikel erwähnten Szene)
Im Bild oben führte es dazu, dass der gleichzeitige Benutzer B ebenfalls „erfolgreich kaufte“, sodass eine weitere Person das Produkt erhalten konnte. Dieses Szenario kann in Situationen mit hoher Parallelität sehr leicht auftreten.

Optimierungsplan 1Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt): Setzen Sie das Feld „Inventarnummer“ auf „ohne Vorzeichen“, da das Feld keine negative Zahl sein darf, wird „falsch“ zurückgegeben

 <?php  //优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
 include(&#39;./mysql.php&#39;);
 $username = &#39;wang&#39;.rand(0,1000);
 //生成唯一订单
 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,$username){
     global $conn;
     $sql="insert into ih_log(event,type,usernma)
     values(&#39;$event&#39;,&#39;$type&#39;,&#39;$username&#39;)";
     return mysqli_query($conn,$sql);
 }
 function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number)
 {
       global $conn;
       $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number)
       values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;,&#39;$username&#39;,&#39;$number&#39;)";
      return  mysqli_query($conn,$sql);
 }
 //模拟下单操作
 //库存是否大于0
 $sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; ";
 $rs=mysqli_query($conn,$sql);
 $row = $rs->fetch_assoc();
   if($row['number']>0){//高并发下会导致超卖
       if($row['number']0";
       $store_rs=mysqli_query($conn,$sql);
       if($store_rs){
           //生成订单
           insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number);
           insertLog('库存减少成功',1,$username);
       }else{
           insertLog('库存减少失败',2,$username);
       }
   }else{
       insertLog('库存不够',3,$username);
   }
 ?>

Pessimistische Sperridee

Es gibt viele Ideen Um die Thread-Sicherheit zu lösen, können Sie die Diskussion aus der Richtung des „pessimistischen Sperrens“ beginnen.

    Pessimistische Sperre, das heißt, wenn Daten geändert werden, wird der gesperrte Zustand übernommen, um Änderungen von externen Anforderungen auszuschließen. Wenn Sie auf einen gesperrten Zustand stoßen, müssen Sie warten.

虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

优化方案2:使用MySQL的事务,锁住操作的行

 <?php  //优化方案2:使用MySQL的事务,锁住操作的行
 include(&#39;./mysql.php&#39;);
 //生成唯一订单号
 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;)";
     mysqli_query($conn,$sql);
 }
 //模拟下单操作
 //库存是否大于0
 mysqli_query($conn,"BEGIN");  //开始事务
 $sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
 $rs=mysqli_query($conn,$sql);
 $row=$rs->fetch_assoc();
 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=mysqli_query($conn,$sql);
     //库存减少
     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
     $store_rs=mysqli_query($conn,$sql);
     if($store_rs){
       echo '库存减少成功';
         insertLog('库存减少成功');
         mysqli_query($conn,"COMMIT");//事务提交即解锁
     }else{
       echo '库存减少失败';
         insertLog('库存减少失败');
     }
 }else{
   echo '库存不够';
     insertLog('库存不够');
     mysqli_query($conn,"ROLLBACK");
 }
 ?>
  1. FIFO队列思路

那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt)

然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

  1. 文件锁的思路
    对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失

优化方案4:使用非阻塞的文件排他锁

 <?php  //优化方案4:使用非阻塞的文件排他锁
 include (&#39;./mysql.php&#39;);
 //生成唯一订单号
 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;)";
     mysqli_query($conn,$sql);
 }
 $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 =  mysqli_query($conn,$sql);
 $row = $rs->fetch_assoc();
 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 =  mysqli_query($conn,$sql);
     //库存减少
     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
     $store_rs =  mysqli_query($conn,$sql);
     if($store_rs){
       echo '库存减少成功';
         insertLog('库存减少成功');
         flock($fp,LOCK_UN);//释放锁
     }else{
       echo '库存减少失败';
         insertLog('库存减少失败');
     }
 }else{
   echo '库存不够';
     insertLog('库存不够');
 }
 fclose($fp);
  ?>
 <?php  //优化方案4:使用非阻塞的文件排他锁
 include (&#39;./mysql.php&#39;);
 //生成唯一订单号
 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;)";
     mysqli_query($conn,$sql);
 }
 $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 =  mysqli_query($conn,$sql);
 $row = $rs->fetch_assoc();
 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 =  mysqli_query($conn,$sql);
     //库存减少
     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
     $store_rs =  mysqli_query($conn,$sql);
     if($store_rs){
       echo '库存减少成功';
         insertLog('库存减少成功');
         flock($fp,LOCK_UN);//释放锁
     }else{
       echo '库存减少失败';
         insertLog('库存减少失败');
     }
 }else{
   echo '库存不够';
     insertLog('库存不够');
 }
 fclose($fp);
  ?>
  1. 乐观锁思路

这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

Wie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt)
有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

优化方案5:Redis中的watch

 <?php  $redis = new redis();
  $result = $redis->connect('127.0.0.1', 6379);
  echo $mywatchkey = $redis->get("mywatchkey");
 /*
   //插入抢购数据
  if($mywatchkey>0)
  {
      $redis->watch("mywatchkey");
   //启动一个新的事务。
     $redis->multi();
    $redis->set("mywatchkey",$mywatchkey-1);
    $result = $redis->exec();
    if($result) {
       $redis->hSet("watchkeylist","user_".mt_rand(1,99999),time());
       $watchkeylist = $redis->hGetAll("watchkeylist");
         echo "抢购成功!<br>";
         $re = $mywatchkey - 1;  
         echo "剩余数量:".$re."<br>";
         echo "用户列表:<pre class="brush:php;toolbar:false">";
         print_r($watchkeylist);
    }else{
       echo "手气不好,再抢购!";exit;
    } 
  }else{
      // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");
      //  $watchkeylist = $redis->hGetAll("watchkeylist");
         echo "fail!<br>";   
         echo ".no result<br>";
         echo "用户列表:<pre class="brush:php;toolbar:false">";
       //  var_dump($watchkeylist); 
  }*/
 $rob_total = 100;   //抢购数量
 if($mywatchkeywatch("mywatchkey");
     $redis->multi(); //在当前连接上启动一个新的事务。
     //插入抢购数据
     $redis->set("mywatchkey",$mywatchkey+1);
     $rob_result = $redis->exec();
     if($rob_result){
          $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey);
         $mywatchlist = $redis->hGetAll("watchkeylist");
         echo "抢购成功!<br>";
       
         echo "剩余数量:".($rob_total-$mywatchkey-1)."<br>";
         echo "用户列表:<pre class="brush:php;toolbar:false">";
         var_dump($mywatchlist);
     }else{
           $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao');
         echo "手气不好,再抢购!";exit;
     }
 }
 ?>

推荐学习:《PHP视频教程

Das obige ist der detaillierte Inhalt vonWie löst man Probleme mit hoher Parallelität mit PHP? (Quellcode beigefügt). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen