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 相关文章推荐
php4的session功能评述(二)
Oct 09 PHP
PHP脚本数据库功能详解(中)
Oct 09 PHP
php面向对象的方法重载两种版本比较
Sep 08 PHP
session在PHP大型web应用中的使用
Jun 25 PHP
使用PHP获取当前url路径的函数以及服务器变量
Jun 29 PHP
php 模拟 asp.net webFrom 按钮提交事件实例
Oct 13 PHP
php递归删除目录与文件的方法
Jan 30 PHP
基于php的微信公众平台开发入门实例
Apr 15 PHP
PHP处理CSV表格文件的常用操作方法总结
Jul 01 PHP
thinkphp实现附件上传功能
May 26 PHP
thinkPHP通用控制器实现方法示例
Nov 23 PHP
解决PHPstudy Apache无法启动的问题【亲测有效】
Oct 30 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实现根据IP地址获取其所在省市的方法
2015/04/30 PHP
Javascript实例教程(19) 使用HoTMetal(2)
2006/12/23 Javascript
基于jquery的横向滚动条(滑动条)
2011/02/24 Javascript
不使用XMLHttpRequest实现异步加载 Iframe和script
2012/10/29 Javascript
js如何调用qq互联api实现第三方登录
2014/03/28 Javascript
JavaScript设计模式之观察者模式(发布者-订阅者模式)
2014/09/24 Javascript
jQuery实现仿腾讯视频列表分页效果的方法
2015/08/07 Javascript
ajax如何实现页面局部跳转与结果返回
2015/08/24 Javascript
js全选按钮的实现方法
2015/11/17 Javascript
AngularJS使用angular-formly进行表单验证
2015/12/27 Javascript
jQuery禁用键盘后退屏蔽F5刷新及禁用右键单击
2016/01/22 Javascript
基于Bootstrap里面的Button dropdown打造自定义select
2016/05/30 Javascript
零基础轻松学JavaScript闭包
2016/12/30 Javascript
vue + socket.io实现一个简易聊天室示例代码
2017/03/06 Javascript
实例详解JavaScript中setTimeout函数的执行顺序
2017/07/12 Javascript
JavaScript设计模式之缓存代理模式原理与简单用法示例
2018/08/07 Javascript
vue组件 keep-alive 和 transition 使用详解
2019/10/11 Javascript
js正则表达式简单校验方法
2021/01/03 Javascript
python写日志封装类实例
2015/06/28 Python
详解python使用Nginx和uWSGI来运行Python应用
2018/01/09 Python
Python如何实现动态数组
2019/11/02 Python
python matplotlib饼状图参数及用法解析
2019/11/04 Python
python selenium xpath定位操作
2020/09/01 Python
python与js主要区别点总结
2020/09/13 Python
Jupyter安装拓展nbextensions及解决官网下载慢的问题
2021/03/03 Python
使用layui框架实现点击左侧导航切换右侧内容且右侧选项卡跟随变化的效果
2020/11/10 HTML / CSS
伯利陶器:Burleigh Pottery
2018/01/03 全球购物
竞选班长演讲稿
2013/12/30 职场文书
《花木兰》教学反思
2014/04/09 职场文书
车辆年检委托书范本
2014/10/14 职场文书
工作表现证明
2015/06/15 职场文书
党员转正大会主持词
2015/07/02 职场文书
php随机生成验证码,php随机生成数字,php随机生成数字加字母!
2021/04/01 PHP
使用python向MongoDB插入时间字段的操作
2021/05/18 Python
基于angular实现树形二级表格
2021/10/16 Javascript
Android Studio 计算器开发
2022/05/20 Java/Android