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和XSS跨站攻击的防范
Apr 17 PHP
一个简单php扩展介绍与开发教程
Aug 19 PHP
PHP多线程之内部多线程实例分析
Mar 09 PHP
实例讲解php数据访问
May 09 PHP
全面解析PHP验证码的实现原理 附php验证码小案例
Aug 17 PHP
thinkphp3.2.0 setInc方法 源码全面解析
Jan 29 PHP
PHP实现用户异地登录提醒功能的方法【基于thinkPHP框架】
Mar 15 PHP
PHP实现多图上传和单图上传功能
May 17 PHP
PHP strripos函数用法总结
Feb 11 PHP
PHP+百度AI OCR文字识别实现了图片的文字识别功能
May 08 PHP
php多进程并发编程防止出现僵尸进程的方法分析
Feb 28 PHP
解决PHP Opcache 缓存刷新、代码重载出现无法更新代码的问题
Aug 24 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 Hash算法:Times33算法代码实例
2015/05/13 PHP
PHP切割汉字的常用方法实例总结
2019/04/27 PHP
使用JS操作页面表格,元素的一些技巧
2007/02/02 Javascript
点图片上一页下一页翻页效果
2008/07/09 Javascript
一些技巧性实用js代码小结
2009/10/14 Javascript
验证javascript中Object和Function的关系的三段简单代码
2010/06/27 Javascript
jQuery修改li下的样式以及li下的img的src的值的方法
2014/11/02 Javascript
js实现可兼容IE、FF、Chrome、Opera及Safari的音乐播放器
2015/02/11 Javascript
Javascript实现商品秒杀倒计时(时间与服务器时间同步)
2015/09/16 Javascript
jquery.cookie实现的客户端购物车操作实例
2015/12/24 Javascript
实例详解AngularJS实现无限级联动菜单
2016/01/15 Javascript
AngularJS 表达式详细讲解及实例代码
2016/07/26 Javascript
js从外部获取图片的实现方法
2016/08/05 Javascript
AngularJS删除路由中的#符号的方法
2016/09/20 Javascript
js中bool值的转换及“&amp;&amp;”、“||”、 “!!”详解
2017/12/21 Javascript
Javascript网页抢红包外挂实现分享
2018/01/11 Javascript
使用Angular Cli如何创建Angular私有库详解
2019/01/30 Javascript
JS实现的进制转换,浮点数相加,数字判断操作示例
2019/11/09 Javascript
vue 页面回退mounted函数不执行的解决方案
2020/07/26 Javascript
JavaScript/TypeScript 实现并发请求控制的示例代码
2021/01/18 Javascript
[01:06:30]DOTA2-DPC中国联赛定级赛 Phoenix vs DLG BO3第二场 1月9日
2021/03/11 DOTA
[06:59]DOTA2-DPC中国联赛3月7日Recap集锦
2021/03/11 DOTA
使用Python神器对付12306变态验证码
2016/01/05 Python
Python爬虫实现网页信息抓取功能示例【URL与正则模块】
2017/05/18 Python
django的ORM操作 删除和编辑实现详解
2019/07/24 Python
Python3实现监控新型冠状病毒肺炎疫情的示例代码
2020/02/13 Python
Python修改列表值问题解决方案
2020/03/06 Python
Python爬虫代理池搭建的方法步骤
2020/09/28 Python
Crucial英睿达法国官网:内存条及SSD固态硬盘升级
2018/07/13 全球购物
最新远光软件笔试题面试题内容
2013/11/08 面试题
大学生学业生涯规划
2014/01/05 职场文书
快餐店的创业计划书范文
2014/01/29 职场文书
大学生社会实践活动总结
2014/07/03 职场文书
2014年医院后勤工作总结
2014/12/06 职场文书
2015年大学宣传部工作总结
2015/05/26 职场文书
python基础之模块的导入
2021/10/24 Python