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 相关文章推荐
Win2003服务器安全加固设置--进一步提高服务器安全性
May 23 PHP
php下过滤html代码的函数 提高程序安全性
Mar 02 PHP
解析Linux下Varnish缓存的配置优化
Jun 20 PHP
php读取文件内容到数组的方法
Mar 16 PHP
php隐藏实际地址的文件下载方法
Apr 18 PHP
php示例详解Constructor Prototype Pattern 原型模式
Oct 15 PHP
php PDO实现的事务回滚示例
Mar 23 PHP
PHP7基于curl实现的上传图片功能
May 11 PHP
PHP实现唤起微信支付功能
Feb 18 PHP
PHP 实现base64编码文件上传出现问题详解
Sep 01 PHP
php使用Swoole实现毫秒级定时任务的方法
Sep 04 PHP
discuz论坛更换域名,详细文件修改步骤
Dec 09 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
Win7 64位系统下PHP连接Oracle数据库
2014/08/20 PHP
PHP实现根据图片色界在不同位置加水印的方法
2015/08/08 PHP
基于PHP实现微信小程序客服消息功能
2019/08/12 PHP
thinkphp框架表单数组实现图片批量上传功能示例
2020/04/04 PHP
Laravel框架源码解析之模型Model原理与用法解析
2020/05/14 PHP
选择TreeView控件的树状数据节点的JS方法(jquery)
2010/02/06 Javascript
jquery $.ajax()取xml数据的小问题解决方法
2010/11/20 Javascript
location对象的属性和方法应用(解析URL)
2013/04/12 Javascript
Js+php实现异步拖拽上传文件
2015/06/23 Javascript
JS模拟键盘打字效果的方法
2015/08/05 Javascript
Javascript数组Array基础介绍
2016/03/13 Javascript
nodejs如何获取时间戳与时间差
2016/08/03 NodeJs
用move.js库实现百叶窗特效
2017/02/08 Javascript
jQuery插件HighCharts实现的2D回归直线散点效果示例【附demo源码下载】
2017/03/09 Javascript
AngulaJS路由 ui-router 传参实例
2017/04/28 Javascript
Bootstrap Table中的多选框删除功能
2018/07/15 Javascript
分享vue里swiper的一些坑
2018/08/30 Javascript
vue.js基于v-for实现批量渲染 Json数组对象列表数据示例
2019/08/03 Javascript
javascript实现动态时钟的启动和停止
2020/07/29 Javascript
[07:57]DOTA2热力大趴狂欢夜 广州站活动回顾
2013/11/27 DOTA
[01:07:41]IG vs VGJ.T 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
python中文编码问题小结
2014/09/28 Python
python爬取亚马逊书籍信息代码分享
2017/12/09 Python
python初学之用户登录的实现过程(实例讲解)
2017/12/23 Python
python实现将excel文件转化成CSV格式
2018/03/22 Python
PySide和PyQt加载ui文件的两种方法
2019/02/27 Python
Python 如何查找特定类型文件
2020/08/17 Python
HTML5 Canvas——用路径描画线条实例介绍
2013/06/09 HTML / CSS
菲律宾票务网站:StubHub菲律宾
2018/04/21 全球购物
iHerb中文官网:维生素、保健品和健康产品
2018/11/01 全球购物
同步和异步有何异同,在什么情况下分别使用他们?
2012/12/28 面试题
个性大学生自我评价
2013/12/04 职场文书
酒店员工职业生涯规划
2014/02/25 职场文书
关于环保的活动方案
2014/08/25 职场文书
小学竞选班长演讲稿
2014/09/09 职场文书
2015年社区工会工作总结
2015/05/26 职场文书