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 和 MySQL 基础教程(四)
Oct 09 PHP
php 过滤器实现代码
Aug 09 PHP
php 实现进制转换(二进制、八进制、十六进制)互相转换实现代码
Oct 22 PHP
PHP表单递交控件名称含有点号(.)会被转化为下划线(_)的处理方法
Jan 06 PHP
PHP在线生成二维码(google api)的实现代码详解
Jun 04 PHP
PHP产生不重复随机数的5个方法总结
Nov 12 PHP
PHP中创建和验证哈希的简单方法实探
Jul 06 PHP
微信公众平台DEMO(PHP)
May 04 PHP
PHP实现时间比较和时间差计算的方法示例
Jul 24 PHP
PHP实现的数据对象映射模式详解
Mar 20 PHP
Laravel 添加多语言提示信息的方法
Sep 29 PHP
PhpStorm的使用教程(本地运行PHP+远程开发+快捷键)
Mar 26 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 魔术函数使用说明
2010/05/14 PHP
基于PHP实现假装商品限时抢购繁忙的效果
2015/10/16 PHP
php中二分法查找算法实例分析
2016/09/22 PHP
CSS(js)限制页面显示的文本字符长度
2012/12/27 Javascript
javascript检查浏览器是否支持flash的实现代码
2014/08/14 Javascript
JS+CSS模拟可以无刷新显示内容的留言板实例
2015/03/03 Javascript
js中函数声明与函数表达式
2015/06/03 Javascript
JavaScript 模块的循环加载实现方法
2015/12/13 Javascript
jQuery基于排序功能实现上移、下移的方法
2016/11/26 Javascript
vue-resource调用promise取数据方式详解
2017/07/21 Javascript
原生JS实现Ajax跨域请求flask响应内容
2017/10/24 Javascript
jQuery中图片展示插件highslide.js的简单dom
2018/04/22 jQuery
微信小程序使用wx.request请求服务器json数据并渲染到页面操作示例
2019/03/30 Javascript
微信小程序 调用远程接口 给全局数组赋值代码实例
2019/08/13 Javascript
js 函数性能比较方法
2020/08/24 Javascript
[01:04:14]OG vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python 命令行非阻塞输入的小例子
2013/09/27 Python
python绘图方法实例入门
2015/05/19 Python
Python中property函数用法实例分析
2018/06/04 Python
Python数据报表之Excel操作模块用法分析
2019/03/11 Python
Python远程视频监控程序的实例代码
2019/05/05 Python
基于torch.where和布尔索引的速度比较
2020/01/02 Python
python传到前端的数据,双引号被转义的问题
2020/04/03 Python
python hmac模块验证客户端的合法性
2020/11/07 Python
德国狗狗用品在线商店:Schecker
2017/03/17 全球购物
孤独星球出版物:Lonely Planet Publications
2018/03/17 全球购物
Simons官方网站:加拿大时尚零售商
2020/02/20 全球购物
环境保护与污染治理求职信
2014/07/16 职场文书
银行竞聘上岗演讲稿
2014/09/12 职场文书
党的群众路线教育实践活动整改落实情况自查报告
2014/10/28 职场文书
2014司机年终工作总结
2014/12/05 职场文书
2015年街道办事处工作总结
2015/05/22 职场文书
Mysql MVCC机制原理详解
2021/04/20 MySQL
Python+Matplotlib+LaTeX玩转数学公式
2022/02/24 Python
疑《守望先锋2》A测截图泄露 或将推出新模式、新界面
2022/04/03 其他游戏
详解Go语言中Get/Post请求测试
2022/06/01 Golang