php处理抢购类功能的高并发请求


Posted in PHP onFebruary 08, 2018

本文以抢购、秒杀为例。介绍如何在高并发状况下确保数据正确。
在高并发请求下容易参数两个问题
1.数据出错,导致产品超卖。
2.频繁操作数据库,导致性能下降。

测试环境

Windows7
apache2.4.9
php5.5.12
php框架 yii2.0
工具 apache bench (apache自带高并发请求工具)。

通常处理方法

从控制器可以看出代码思路。先查询商品库存。如果库存大于0 ,则库存减少1,同时生产订单,录入抢购者数据。

// 常规代码处理高并发
  public function actionNormal(){
    // 查询库存
    $stock = Goods::find()->select('stock')->where(['goods_id'=>100001])->asArray()->one();
    // 判断该商品是否还有库存
    if ($stock['stock']>0) {
      // 库存减一
      Goods::updateAllCounters(['stock' => -1],['goods_id'=>100001]);

      // 生产订单(另外功能,暂且随机赋值)
      $order = $this->build_order();

      // 秒杀信息入库
      $model = new Highly();
      $model->order_id = $order;
      $model->goods_name = '秒杀商品';
      $model->buy_time = date('Y-m-d H:i:s',time());
      $model->mircrotime = microtime(true);
      if($model->save()===false){
        echo '未能成功抢购!';
      }else{
        echo '恭喜你,订单<b>'.$order.'</b>抢购成功';
      }

    }else{
      echo '已被抢购一空!';
    }
  }

将商品库存设置为20后,通过ab 配置200的并发请求。

ab -n 200 -c 200 http//localhost/highly/normal

执行结果发现库存变成了负值,商品超卖了。

php处理抢购类功能的高并发请求

原因比较简单,在高并发请求下。在生产订单,减少库存之前,会优先查询到库存结果。

优化一:修改库存数据类型

第一种优化方法,从数据库入手。既然查询到的结果不准确,那我就在库存减少上做手脚。将库存的数据类型改成无符号(不能有负值)。

代码还是跟上面差不多,只是在库存减1的地方做了个判断。避免报错。

public function actionNormal(){
    // 查询库存
    $stock = Goods::find()->select('stock')->where(['goods_id'=>100001])->asArray()->one();
    // 判断该商品是否还有库存
    if ($stock['stock']>0) {
      // 库存减一
      if(Goods::updateAllCounters(['stock' => -1],['goods_id'=>100001])===false){
        echo "已被抢购一空!";
        return false;
      }

      // 生产订单(另外功能,暂且随机赋值)
      $order = $this->build_order();

      // 秒杀信息入库
      $model = new Highly();
      $model->order_id = $order;
      $model->goods_name = '秒杀商品';
      $model->buy_time = date('Y-m-d H:i:s',time());
      $model->mircrotime = microtime(true);
      if($model->save()===false){
        echo '未能成功抢购!';
      }else{
        echo '恭喜你,订单<b>'.$order.'</b>抢购成功';
      }

    }else{
      echo '已被抢购一空!';
    }
  }

这一次同样200的并发,执行结果发现。数据正确,并不会出现超卖的情况。
思路其实也比较简单。因为库存不能为负值,当库存等于0时,如果还有值传进来,则会报错。请求被终止。

这种优化方式,虽然避免了商品超卖的情况。但是在另一方面,请求仍然会对数据库造成压力。如果多个功能使用此数据库,会造成性能下降厉害。

优化二:redis

利用 redis list类型的pop的原子性。在操作数据库前,做一个验证。当商品卖完后,就不允许再继续进行数据库操作。

// redis list 高并发测试
  public function actionRedis(){
    $redis = \Yii::$app->redis;
    // $redis->lpush('mytest',1);
    $order = $this->build_order();
    // echo $order;die;
    // echo $redis->llen('mytest');
    $reg = $redis->lpop('mytest');
    if (!$reg) {
      echo "笨蛋!已经被抢光啦!";
      return false;
    }
    $redis->close();
    $model = new Highly();
    $model->order_id = $order;
    $model->goods_name = '秒杀商品';
    $model->buy_time = date('Y-m-d H:i:s',time());
    $model->mircrotime = microtime(true);

    if($model->save()===false){
      echo '未能成功抢购!';
    }else{
      echo '恭喜你,订单<b>'.$order.'</b>抢购成功';
    }
  }
  // 给redis添加商品
  public function actionInsertgoods(){
    $count = yii::$app->request->get('count',0);
    if (empty($count)) {
      echo '大兄弟,你还没告诉我需要上架多少商品呢!';
      return false;
    }
    $redis = \Yii::$app->redis;
    for ($i=0; $i < $count; $i++) { 
      $redis->lpush('mytest',1);
    }
    echo '成功添加了'.$redis->llen('mytest').'件商品。';
    $redis->close();

  }

这点的代码,我写了两个方法。第一个方法是秒杀的代码,第二个方法是给秒杀的商品设置数量。为了方便测试,我这里处理的比较简单。

通过测试,数据库生产的订单数量正常,并没有出现问题。而又避免了请求数据库造成性能下降的问题。同时内存数据库redis查询的速度要比mysql快很多。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
php下将多个数组合并成一个数组的方法与实例代码
Feb 03 PHP
在PHP中利用wsdl创建标准webservice的实现代码
Dec 07 PHP
做了CDN获取用户真实IP的函数代码(PHP与Asp设置方式)
Apr 13 PHP
PHP执行批量mysql语句的解决方法
May 02 PHP
PHP以mysqli方式连接类完整代码实例
Jul 15 PHP
ThinkPHP查询返回简单字段数组的方法
Aug 25 PHP
PHP 获取ip地址代码汇总
Jul 05 PHP
PHP文件与目录操作示例
Dec 24 PHP
PHP中使用CURL发送get/post请求上传图片批处理功能
Oct 15 PHP
PHP实现与java 通信的插件使用教程
Aug 11 PHP
Yii框架where查询用法实例分析
Oct 22 PHP
PHP对接阿里云虚拟号的实现(号码隐私保护)
Apr 06 PHP
php+redis实现商城秒杀功能
Nov 19 #PHP
php+redis消息队列实现抢购功能
Feb 08 #PHP
PHP多线程模拟实现秒杀抢单
Feb 07 #PHP
PHP设计模式之装饰器模式实例详解
Feb 07 #PHP
PHP使用星号替代用户名手机和邮箱的实现代码
Feb 07 #PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
Feb 07 #PHP
php删除一个路径下的所有文件夹和文件的方法
Feb 07 #PHP
You might like
php记录代码执行时间(实现代码)
2013/07/05 PHP
PHP实现基于栈的后缀表达式求值功能
2017/11/10 PHP
php实现往pdf中加数字签名操作示例【附源码下载】
2018/08/07 PHP
php fread函数使用方法总结
2019/05/28 PHP
jQuery ajax serialize()方法的使用以及常见问题解决
2013/01/27 Javascript
JS Pro-深入面向对象的程序设计之继承的详解
2013/05/07 Javascript
js获取窗口相对于屏幕左边和上边的位置坐标
2014/05/15 Javascript
Node.js开发之访问Redis数据库教程
2015/01/14 Javascript
javascript实现鼠标拖动改变层大小的方法
2015/04/30 Javascript
laypage分页控件使用实例详解
2016/05/19 Javascript
Angular使用ng-messages与PHP进行表单数据验证
2016/12/28 Javascript
JS批量替换内容中关键词为超链接
2017/02/20 Javascript
angularJS之$http:与服务器交互示例
2017/03/17 Javascript
JS手机端touch事件计算滑动距离的方法示例
2017/10/26 Javascript
layui table单元格事件修改值的方法
2019/09/24 Javascript
如何基于jQuery实现五角星评分
2020/09/02 jQuery
[14:03]2017DOTA2亚洲邀请赛开幕式:12神兵演绎水墨中华
2017/04/01 DOTA
详细介绍Ruby中的正则表达式
2015/04/10 Python
Python中List.count()方法的使用教程
2015/05/20 Python
深入学习python的yield和generator
2016/03/10 Python
flask框架视图函数用法示例
2018/07/19 Python
Python面向对象之类和实例用法分析
2019/06/08 Python
CSS3截取字符串实例代码【推荐】
2018/06/07 HTML / CSS
BOSE德国官网:尽探索之力,享音乐之极
2016/12/11 全球购物
世界上最大的汽车共享网站:Zipcar
2017/01/14 全球购物
Space NK美国站:英国高端美妆护肤商城
2017/05/22 全球购物
描述内存分配方式以及它们的区别
2016/10/15 面试题
解释下列WebService名词:WSDL、SOAP、UDDI
2012/06/22 面试题
大专毕业自我鉴定
2014/02/04 职场文书
各营销点岗位职责范本
2014/03/05 职场文书
学生会部长竞聘书
2014/03/31 职场文书
安全目标责任书
2014/07/22 职场文书
行政执法队伍作风整顿个人剖析材料
2014/10/11 职场文书
2014大学辅导员工作总结
2014/12/02 职场文书
员工升职自我评价
2019/03/26 职场文书
深入理解mysql事务隔离级别和存储引擎
2022/04/12 MySQL