热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PHP高并发问题如何解决

PHP高并发问题如何解决?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你

PHP高并发问题如何解决?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

重启与过载保护

如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回

高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

  1. 超发的原因

假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

PHP高并发问题如何解决

在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

 fetch_assoc();
   if($row['number']>0){//高并发下会导致超卖
       if($row[&#39;number&#39;]<$number){
         return insertLog(&#39;库存不够&#39;,3,$username);
       }
       $order_sn=build_order_no();
       //库存减少
       $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39; and number>0";
       $store_rs=mysqli_query($conn,$sql);
       if($store_rs){
           //生成订单
           insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number);
           insertLog(&#39;库存减少成功&#39;,1,$username);
       }else{
           insertLog(&#39;库存减少失败&#39;,2,$username);
       }
   }else{
       insertLog(&#39;库存不够&#39;,3,$username);
   }
 ?>
  1. 悲观锁思路

解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

PHP高并发问题如何解决

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

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

 fetch_assoc();
 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=mysqli_query($conn,$sql);
     //库存减少
     $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
     $store_rs=mysqli_query($conn,$sql);
     if($store_rs){
       echo &#39;库存减少成功&#39;;
         insertLog(&#39;库存减少成功&#39;);
         mysqli_query($conn,"COMMIT");//事务提交即解锁
     }else{
       echo &#39;库存减少失败&#39;;
         insertLog(&#39;库存减少失败&#39;);
     }
 }else{
   echo &#39;库存不够&#39;;
     insertLog(&#39;库存不够&#39;);
     mysqli_query($conn,"ROLLBACK");
 }
 ?>
  1. FIFO队列思路

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

PHP高并发问题如何解决

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

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

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

 fetch_assoc();
 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 =  mysqli_query($conn,$sql);
     //库存减少
     $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
     $store_rs =  mysqli_query($conn,$sql);
     if($store_rs){
       echo &#39;库存减少成功&#39;;
         insertLog(&#39;库存减少成功&#39;);
         flock($fp,LOCK_UN);//释放锁
     }else{
       echo &#39;库存减少失败&#39;;
         insertLog(&#39;库存减少失败&#39;);
     }
 }else{
   echo &#39;库存不够&#39;;
     insertLog(&#39;库存不够&#39;);
 }
 fclose($fp);
  ?>
 fetch_assoc();
 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 =  mysqli_query($conn,$sql);
     //库存减少
     $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
     $store_rs =  mysqli_query($conn,$sql);
     if($store_rs){
       echo &#39;库存减少成功&#39;;
         insertLog(&#39;库存减少成功&#39;);
         flock($fp,LOCK_UN);//释放锁
     }else{
       echo &#39;库存减少失败&#39;;
         insertLog(&#39;库存减少失败&#39;);
     }
 }else{
   echo &#39;库存不够&#39;;
     insertLog(&#39;库存不够&#39;);
 }
 fclose($fp);
  ?>
  1. 乐观锁思路

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

PHP高并发问题如何解决
有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

优化方案5:Redis中的watch

 connect(&#39;127.0.0.1&#39;, 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 "抢购成功!
";          $re = $mywatchkey - 1;            echo "剩余数量:".$re."
";          echo "用户列表:
";
         print_r($watchkeylist);
    }else{
       echo "手气不好,再抢购!";exit;
    } 
  }else{
      // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");
      //  $watchkeylist = $redis->hGetAll("watchkeylist");
         echo "fail!
";             echo ".no result
";          echo "用户列表:
";
       //  var_dump($watchkeylist); 
  }*/
 $rob_total = 100;   //抢购数量
 if($mywatchkey<=$rob_total){
     $redis->watch("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 "抢购成功!
";                  echo "剩余数量:".($rob_total-$mywatchkey-1)."
";          echo "用户列表:
";
         var_dump($mywatchlist);
     }else{
           $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),&#39;meiqiangdao&#39;);
         echo "手气不好,再抢购!";exit;
     }
 }
 ?>


看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注编程笔记行业资讯频道,感谢您对编程笔记的支持。

php有什么特点

1、执行速度快。2、具有很好的开放性和可扩展性。3、PHP支持多种主流与非主流的数据库。4、面向对象编程:PHP提供了类和对象。5、版本更新速度快。6、具有丰富的功能。7、可伸缩性。8、功能全面,包括图形处理、编码与解码、压缩文件处理、xml解析等。


推荐阅读
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • 本文介绍了自动化测试专家Elfriede Dustin在2008年的文章中讨论了自动化测试项目失败的原因。同时,引用了IDT在2007年进行的一次软件自动化测试的研究调查结果,调查显示很多公司认为自动化测试很有用,但很少有公司成功实施。调查结果表明,缺乏资源是导致自动化测试失败的主要原因,其中37%的人认为缺乏时间。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
author-avatar
Vicky小希希
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有